I have this code in a model called Project. It sets a owner to a project before the project is saved.
before_save :set_owner
# Set the owner of the project right before it is saved.
def set_owner
self.owner_id = mock_model(User).id # current_user.id is stubbed out for a mock_model.
# Lifecycle is set by the form's collection_select
end
And current_user function is stubbed out in my Rspec tests to return a mock_model (which is why the code above is showing mock_model instead of current_user).
Now, when I run this, my Rspec tests break, and complains:
undefined method `mock_model' for #<Project:0x105c70af0>
My guess, is that since before_save is a instance function, it somehow thinks that mock_model is a function defined in Project.
Someone must have encountered this before... Any way around it?
Two things immediately stand out:
You shouldn't be using mock_model in your actual Project model. All test code should remain in the specs.
You cannot pass the current_user object from the controller to the model (at least not in any way you should).
I would use an attr_accessor in your project model to set the current_user id.
class Project < AR::Base
attr_accessor :current_user
def set_owner
self.owner_id = current_user.id unless current_user.nil?
end
end
Then your spec should look something more along the lines of:
it "should set the owner id" do
user = mock_model(User)
project = Project.new
project.current_user = user
project.save
project.owner_id.should == user.id
end
Related
There are many gems online that have this behaviour
for example Fabrication Gem has this behaviour
you can create an object via
Fabricate(:person)
Fabricate.create(:person)
and the this is the code
but when I tried to mimic that pattern
# app/services/permissions.rb
class Permissions
def self.call(user)
# ...
end
end
def Permissions(user)
Permissions.call(user)
end
but Permissions(user) always return error undefined method 'Permissions' for #<Ability:0x007ff6ea78de88>
the ability model is
# app/models/ability.rb
class Ability
# ...
def initialize(user)
if user.role == 'Admin'
# ....
elsif user.role == 'Manager'
Permissions(user) # wont work
Permissions.call(user) # works fine
end
end
end
the code fine but when invoked within a rails app it doesn't work.. so how to make such a behaviour possible in Rails?
Updated yet again based on comments, with a tested workaround: Move the permissions method to the top of ability.rb, and it should work.
The issue is that rails has a strong convention about what can be accessed from different folders. A global method in a service file is not accessible from a model, something similar to the fact that a view helper is not available in a model.
This is how I would replicate it:
def MyClass(something)
"#{something} was created."
end
class MyClass
class << self
alias_method :create, :MyClass # MyClass as in Object's private method!
end
end
MyClass(:rock) #=> "rock was created."
MyClass.create(:gem) #=> "gem was created."
EDIT: I can't comment yet because my account is new, but if I'm not mistaken your Permissions method is being defined within another class/module. What you want to do is to make the Permissions method definition in the main Ruby object, that is, outside of all classes or modules.
I have an ActiveModel::Serializer class (class TaskSerializer < ActiveModel::Serializer) that I'm looking to test.
The serializer makes use of the current_user object, because it's in scope during the controller actions.
But I'm attempting to write a new rspec file. (task_serializer_spec.rb)
When I run
TaskSerializer.new(task).to_json
I get an error saying that the current_user method doesn't exist.
I can't mock the variable because we have "the method must exist" flag on our mocks.
I understand that there are some other parameters that I can pass in the NEW. but I can't find any docs on it. Can someone offer a way to get things like current_user in the scope.
My answer might be a little late, but if someone ever ends up on this page, here is a way to do this:
app/serializers/task_serializer.rb :
class TaskSerializer < ActiveModel::Serializer
attributes :id, :method_using_current_user, :whatever_other_attributes_goes_here
delegate :current_user, to: :scope
def method_using_current_user
current_user.some_method_here
end
end
In your rspec test, you can than do something like this:
RSpec.describe TaskSerializer do
let(:user) { create(:user) }
# or any other user you want to create here
before do
# As current user is delegated to controller scope, we mock both here
allow_any_instance_of(TaskSerializer).to receive(:scope).and_return(ApplicationController.new)
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user)
end
# your test goes here
end
And voila.
Ok, so my main issue is I have implemented Mailboxer into our project to handle messaging and I am trying to write tests for it. However, I keep stumbling over and over again. I have attempted several different stub/mocks but have made no progress.
We have a conversations_controller.rb that relies on before_filters for setting all the instance variables necessary for doing each action. Then in the controller actions, the instance variables are referenced directly to do any sort of action or to return specific data.
Here is an example of our index action which returns all conversations in the "box" that is specified in the before_filter, of the mailbox also specified in another before_filter:
class ConversationsController < ::ApplicationController
before_filter :get_user_mailbox, only: [:index, :new_message, :show_message, :mark_as_read, :mark_as_unread, :create_message, :reply_message, :update, :destroy_message, :untrash]
before_filter :get_box
def index
if #box.eql? "inbox"
#conversations = #mailbox.inbox
elsif #box.eql? "sentbox"
#conversations = #mailbox.sentbox
else
#conversations = #mailbox.trash
end
end
And before filters:
private
def get_user_mailbox
#user = User.where(:user_name => user.user_name.downcase).where(:email => user.email.downcase).first_or_create
#mailbox = #user.mailbox if #user
end
def get_box
if params[:box].blank? or !["inbox","sentbox","trash"].include?params[:box]
params[:box] = 'inbox'
end
#box = params[:box]
end
So I guess I have 2 questions in one. First, how to I get my tests to generate the correct data #mailbox, #user, and #box that is needed for the index action. Next, how do I pass the fake parameter to set #box to different "inbox/sentbox/trash". I have tried controller.index({box: "inbox"}) but always get "wrong arguments 1 for 0" messages.
I have tried the following in various different ways, but always get nil:class errors which means that my instance variables are definitely not being set properly.
describe "GET 'index' returns correct mailbox box" do
before :each do
#user = User.where(:user_name => 'test').where(:email => 'test#test.com').first_or_create
#mailbox = #user.mailbox
end
it "#index returns inbox when box = 'inbox'" do
mock_model User
User.stub_chain(:where, :where).and_return(#user)
controller.index.should == #mailbox.inbox
end
end
Filters and callbacks are hard to test and debug. Avoid them when possible.
In this case I don't think your before_filter is necessary, thus no need to test it. The better home for the methods is model.
Check my refacoring:
class User < ActiveRecord::Base
delegate :inbox, :sentbox, :trash, to: :mailbox
end
class ConversationsController < ::ApplicationController
def index
#conversations = current_user.send get_box
end
private
def get_box
# your code
end
end
That's all. Should be enough.
You can then test regularly.
First of all, read the oficial documentation for rails testing: using data for testing and passing parameters to controlers is explained there.
To generate data for your tests you can:
fill your test database with some mailbox and users using rails fixtures or use something like factory girl
use mock objects to fake data. I personally use the mocha gem but there are others
I tend to use a combination of both, prefering mock objects when possible and falling back to factory girl when mocking needs too much code.
I've got a model class that overrides update_attributes:
class Foo < ActiveRecord::Base
def update_attributes(attributes)
if super(attributes)
#do some other cool stuff
end
end
end
I'm trying to figure out how to set an expectation and/or stub on the super version of update_attributes to make sure that in the success case the other stuff is done. Also I want to make sure that the super method is actually being called at all.
Here's what I have tried so far (and it didn't work, of course):
describe "#update_attributes override" do
it "calls the base class version" do
parameters = Factory.attributes_for(:foo)
foo = Factory(:foo, :title => "old title")
ActiveRecord::Base.should_receive(:update_attributes).once
foo.update_attributes(parameters)
end
end
This doesn't work, of course:
Failure/Error: ActiveRecord::Base.should_recieve(:update_attributes).once
NoMethodError:
undefined method `should_recieve' for ActiveRecord::Base:Class
Any ideas?
update_attributes is an instance method, not a class method, so you cannot stub it directly on ActiveRecord::Base with rspec-mocks, as far as I know. And I don't think that you should: the use of super is an implementation detail that you shouldn't be coupling your test to. Instead, its better to write examples that specify the behavior you want to achieve. What behavior do you get from using super that you wouldn't get if super wasn't used?
As an example, if this was the code:
class Foo < ActiveRecord::Base
def update_attributes(attributes)
if super(attributes)
MyMailer.deliver_notification_email
end
end
end
...then I think the interesting pertinent behavior is that the email is only delivered if there are no validation errors (since that will cause super to return true rather than false). So, I might spec this behavior like so:
describe Foo do
describe "#update_attributes" do
it 'sends an email when it passes validations' do
record = Foo.new
record.stub(:valid? => true)
MyMailer.should_receive(:deliver_notification_email)
record.update_attributes(:some => 'attribute')
end
it 'does not sent an email when it fails validations' do
record = Foo.new
record.stub(:valid? => false)
MyMailer.should_receive(:deliver_notification_email)
record.update_attributes(:some => 'attribute')
end
end
end
Try replacing should_recieve with should_receive.
I'm using the facebooker gem which creates a variable called facebook_session in the controller scope (meaning when I can call facebook_session.user.name from the userscontroller section its okay). However when I'm rewriting the full_name function (located in my model) i can't access the facebook_session variable.
You'll have to pass the value into your model at some point, then store it if you need to access it regularly.
Models aren't allowed to pull data from controllers -- it would break things in console view, unit testing and in a few other situations.
The simplest answer is something like this:
class User
attr_accessor :facebook_name
before_create :update_full_name
def calculated_full_name
facebook_name || "not sure"
end
def update_full_name
full_name ||= calculated_full_name
end
end
class UsersController
def create
#user = User.new params[:user]
#user.facebook_name = facebook_session.user.name
#user.save
end
end