RSpec mocks not being called - ruby-on-rails

I'm trying to create a test on a controller using an rspec mock of a model, and it seems to only work when I say
Type.any_instance.should_recieve(...)
instead of
instancename.should_receive(...)
My code looks like this. (normally I use FactoryGirl, but I am not in this example to make sure that it's not the problem)
it "calls blah on foo" do
foo = Foo.new
foo.save
foo.should_receive(:blah) #this fails because it's called 0 times
#Foo.any_instance.should_receive(:blah) #this would succeed
post :create, {:foo => foo}
end
and in my Controller
def create
foo = Foo.find_by_id(params[:foo])
foo.blah
#other stuff thats not related
end
I know I could mock Foo.find_by_id and have it return foo, but I feel like I shouldn't need to do that because it should be returned anyway, and that means the test would break if I stopped using find_by_id, which is really not an important detail.
Any idea what I'm doing wrong? I feel like my test would be better if I didn't have to say any_instance everywhere and didn't have to mock find_by_id.

Your code does not work because it is not the same foo object that is being called with blah in your actual code.
In your spec code, you create an instance and save it:
foo = Foo.new
foo.save
This saves a record to the db, which foo points to. You then put an expectation on the object:
foo.should_receive(:blah)
This expectation will only work if the spec and app code point to the same object. You can achieve this, as you note, by for example stubbing find_by_id to return it. Alternatively, you can also set an expectation on any instance, which you also note.
However, the expectation will not work as-is. In your actual code, you create a different object foo:
foo = Foo.find_by_id(params[:foo])
foo.blah
If params[:foo] is the id for the record, then both foo in your spec code and foo in your app code point to the same record, but that does not mean that they are the same object (they are not).
Also, if I understand correctly, I believe that this:
post :create, {:foo => foo}
should be:
post :create, {:foo => foo.id}
So, in a nutshell, if what you want is a message expectation, you'll need to either stub find or apply the expectation on any instance. (Note that you should be able to stub find rather than find_by_id, since the dynamic finders call through to find anyway and that should make your test more robust.)
Hope that helps.

Related

How do I test if a method is called on particular objects pulled from the DB in Rails with RSpec?

If I have a User model that includes a method dangerous_action and somewhere I have code that calls the method on a specific subset of users in the database like this:
class UserDanger
def perform_dangerous_action
User.where.not(name: "Fred").each(&:dangerous_action)
end
end
how do I test with RSpec whether it's calling that method on the correct users, without actually calling the method?
I've tried this:
it "does the dangerous thing, but not on Fred" do
allow_any_instance_of(User).to receive(:dangerous_action).and_return(nil)
u1 = FactoryBot.create(:user, name: "Jill")
u2 = FactoryBot.create(:user, name: "Fred")
UserDanger.perform_dangerous_action
expect(u1).to have_recieved(:dangerous_action)
expect(u2).not_to have_recieved(:dangerous_action)
end
but, of course, the error is that the User object doesn't respond to has_recieved? because it's not a double because it's an object pulled from the database.
I think I could make this work by monkey-patching the dangerous_action method and making it write to a global variable, then check the value of the global variable at the end of the test, but I think that would be a really ugly way to do it. Is there any better way?
I realised that I'm really trying to test two aspects of the perform_dangerous_action method. The first is the scoping of the database fetch, and the second is that it calls the correct method on the User objects that come up.
For testing the scoping of the DB fetch, I should really just make a scope in the User class:
scope :not_fred, -> { where.not(name: "Fred") }
which can be easily tested with a separate test.
Then the perform_dangerous_action method becomes
def perform_dangerous_action
User.not_fred.each(&:dangerous_action)
end
and the test to check it calls the right method for not_fred users is
it "does the dangerous thing" do
user_double = instance_double(User)
expect(user_double).to receive(:dangerous_action)
allow(User).to receive(:not_fred).and_return([user_double])
UserDanger.perform_dangerous_action
end
i think, in many cases, you don't want to separate a where or where.not into a scope, in that cases, you could stub ActiveRecord::Relation itself, such as:
# default call_original for all normal `where`
allow_any_instance_of(ActiveRecord::Relation)
.to receive(:where).and_call_original
# stub special `where`
allow_any_instance_of(ActiveRecord::Relation)
.to receive(:where).with(name: "...")
.and_return(user_double)
in your case, where.not is actually call ActiveRecord::QueryMethods::WhereChain#not method so i could do
allow_any_instance_of(ActiveRecord::QueryMethods::WhereChain)
.to receive(:not).with(name: "Fred")
.and_return(user_double)

Find last created record RSpec test

How could I write a test to find the last created record?
This is the code I want to test:
Post.order(created_at: :desc).first
I'm also using factorybot
If you've called your method 'last_post':
def self.last_post
Post.order(created_at: :desc).first
end
Then in your test:
it 'should return the last post' do
expect(Post.last_post).to eq(Post.last)
end
On another note, the easiest way to write your code is simply
Post.last
And you shouldn't really be testing the outcome of ruby methods (you should be making sure the correct ruby methods are called), so if you did:
def self.last_post
Post.last
end
Then your test might be:
it 'should send the last method to the post class' do
expect(Post).to receive(:last)
Post.last_post
end
You're not testing the outcome of the 'last' method call - just that it gets called.
The accepted answer is incorrect. Simply doing Post.last will order the posts by the ID, not by when they were created.
https://apidock.com/rails/ActiveRecord/FinderMethods/last
If you're using sequential IDs (and ideally you shouldn't be) then obviously this will work, but if not then you'll need to specify the column to sort by. So either:
def self.last_post
order(created_at: :desc).first
end
or:
def self.last_post
order(:created_at).last
end
Personally I'd look to do this as a scope rather than a dedicated method.
scope :last_created -> { order(:created_at).last }
This allows you to create some nice chains with other scopes, such as if you had one to find all posts by a particular user/account, you could then chain this pretty cleanly:
Post.for_user(user).last_created
Sure you can chain methods as well, but if you're dealing with Query interface methods I feel scopes just make more sense, and tend to be cleaner.
If you wanted to test that it returns the correct record, in your test you could do something like:
let!(:last_created_post) { factory_to_create_post }
. . .
it "returns the correct post"
expect(Post.last_post).to eq(last_created_post)
end
If you wanted to have an even better test, you could create a couple records before the last record to verify the method under test is pulling the correct result and not just a result from a singular record.

Mocks and Stubs. I don't get the basics

I am in the process of freeing myself from FactoryGirl (at least in the lib folder). So, I start writing strange stuff like "mock" and "stub". Can somebody help a novice out?
I have this module
module LogWorker
extend self
def check_todo_on_log(log, done)
if done == "1"
log.todo.completed = true
log.todo.save!
elsif done.nil?
log.todo.completed = false
log.todo.save!
end
end
end
log and todo are rails models with a todo :has_many logs association. But that should really not matter when working with stubs and mocks, right?
I have tried many things, but when I pass the mock to the method nothing happens,
describe LogWorker do
it 'should check_todo_on_log'do
todo = mock("todo")
log = mock("log")
log.stub!(:todo).and_return(todo)
todo.stub!(:completed).and_return(false)
LogWorker.check_todo_on_log(log,1)
log.todo.completed.should eq true
end
end
Failures:
1) LogWorker should check_todo_on_log
Failure/Error: log.todo.completed.should eq true
expected: true
got: false
(compared using ==
I would really like to see some spec that would test the LogWorker.check_todo_on_log method with stubs and/or mocks.
Firstly, your check_todo_on_log method is pretty bad. Never, ever use strings as options, especially when the string is "1". Also, if you pass "2", nothing happens. I'll assume though it is just a partial method, and your code isn't really like that :P
Looking at your code, you have three main problems. Firstly, you call LogWorker.check_todo_on_log(log,1). This won't do anything, as your method only does stuff when the second param is the string "1" or nil. Secondly, you stub todo.completed so it always returns false: todo.stub!(:completed).and_return(false). You then test if it is true. Obviously this is going to fail. Finally, you don't mock the save! method. I don't know how the code is actually running for you (it doesn't work for me).
Below is how I would write your specs (note that they are testing weird behaviour as the check_todo_on_log method is also strange).
Firstly, there is an easier way to add mock methods to a mock object. You can pass keys and values to the mock methods, and they will automatically be created.
Next, I put the mocks into let blocks. This allows them to be recreated easily for each test. Finally, I add a test for each possible behaviour of the function.
# you won't need these two lines, they just let the code be run by itself
# without a rails app behind it. This is one of the powers of mocks,
# the Todo and Log classes aren't even defined anywhere, yet I can
# still test the `LogWorker` class!
require 'rspec'
require 'rspec/mocks/standalone'
module LogWorker
extend self
def check_todo_on_log(log, done)
if done == "1"
log.todo.completed = true
log.todo.save!
elsif done.nil?
log.todo.completed = false
log.todo.save!
end
end
end
describe LogWorker do
let(:todo) { mock("Todo", save!: true) }
let(:log) { mock("Log", todo: todo) }
describe :check_todo_on_log do
it 'checks todo when done is "1"'do
todo.should_receive(:completed=).with(true)
LogWorker.check_todo_on_log(log,"1")
end
it 'unchecks todo when done is nil'do
todo.should_receive(:completed=).with(false)
LogWorker.check_todo_on_log(log,nil)
end
it "doesn't do anything when done is not '1' or nil" do
todo.should_not_receive(:completed=)
LogWorker.check_todo_on_log(log,3)
end
end
end
Notice how I am using behaviour based testing? I'm not testing that an attribute on the mock has a value, I am checking that an appropriate methods are called on it. This is the key to correctly using mocks.

Very simple Rspec rails test is not passing, where it should

I'm having some problems creating an rspec test to my rails application.
Let say that I have a model called MyModel, with the following function, that obtains the instances of all the MyModels that have an empty text
self.searchEmtpy:
self.where(:text => nil)
end
I've defined the following test, that checks that, new MyModels with empty text, should be returned by the previous function. I use FactoryGirl for the model creation.
describe "get empty models" do
before do
#previousModels=MyModel.searchEmtpy
#newModel=Factory(:myModel, :text => nil)
end
it "new empty models should appear" do
currentModels=MyModel.searchEmtpy
(previousModels << #newModel).should eq(currentModels)
end
end
The test is quite simple, but its not working. I don't know why, but, for what I understand from the output, it seams that, on the "should" line, previousModels already contains the newModel on it, so the test fails (it contains #newModel 2 times.
I'm missing something obvious? Aren't the instructions inside "it" called in order?
To clarify, the following test does not fail, where it should:
describe "get empty models" do
before do
#previousModels=MyModel.searchEmtpy
#newModel=Factory(:myModel, :text => nil)
end
it "new empty models should appear" do
currentModels=MyModel.searchEmtpy
(previousModels).should eq(currentModels)
end
end
self.where(:text => nil)
Is an ActiveRecord::Relation - the query doesn't actually fire until you try to do something to it (like iterate over it, append to it etc.)
In this case that happens on the same line as your call to should, ie after the factory has created the instance.
One way to fix this would be to force the evaluation of the relation in your before block, for example call .all on it.

Stubbing named_scope in an RSpec Controller

I haven't been able to find anything for a situation like this. I have a model which has a named scope defined thusly:
class Customer < ActiveRecord::Base
# ...
named_scope :active_customers, :conditions => { :active => true }
end
and I'm trying to stub it out in my Controller spec:
# spec/customers_controller_spec.rb
describe CustomersController do
before(:each) do
Customer.stub_chain(:active_customers).and_return(#customers = mock([Customer]))
end
it "should retrieve a list of all customers" do
get :index
response.should be_success
Customer.should_receive(:active_customers).and_return(#customers)
end
end
This is not working and is failing, saying that Customer expects active_customers but received it 0 times. In my actual controller for the Index action I have #customers = Customer.active_customers. What am I missing to get this to work? Sadly, I'm finding that it's easier to just write the code than it is to think of a test/spec and write that since I know what the spec is describing, just not how to tell RSpec what I want to do.
I think there's some confusion when it comes to stubs and message expectations. Message expectations are basically stubs, where you can set the desired canned response, but they also test for the call to be made by the code being tested. In contrast stubs are just canned responses to the method calls. But don't mix a stub with a message expectation on the same method and test or bad things will happen...
Back to your question, there are two things (or more?) that require spec'ing here:
That the CustomersController calls Customer#active_customers when you do a get on index. Doesn't really matter what Customer#active_customers returns in this spec.
That the active_customers named_scope does in fact return customers where the active field is true.
I think that you are trying to do number 1. If so, remove the whole stub and simply set the message expectation in your test:
describe CustomersController do
it "should be successful and call Customer#active_customers" do
Customer.should_receive(:active_customers)
get :index
response.should be_success
end
end
In the above spec you are not testing what it returns. That's OK since that is the intent of the spec (although your spec is too close to implementation as opposed to behavior, but that's a different topic). If you want the call to active_customers to return something in particular, go ahead and add .and_returns(#whatever) to that message expectation. The other part of the story is to test that active_customers works as expected (ie: a model spec that makes the actual call to the DB).
You should have the array around the mock if you want to test that you receive back an array of Customer records like so:
Customer.stub_chain(:active_customers).and_return(#customers = [mock(Customer)])
stub_chain has worked the best for me.
I have a controller calling
ExerciseLog.this_user(current_user).past.all
And I'm able to stub that like this
ExerciseLog.stub_chain(:this_user,:past).and_return(#exercise_logs = [mock(ExerciseLog),mock(ExerciseLog)])

Resources