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

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.

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)

Test whether a class level method change attribute of another class

In the Ruby application I have a class(Resque Job) that has method that affect the values of a different class when called with the id of the latter class.
class ResqueKlass
def self.perform(id)
obj = EditKlass.find(id)
obj.update(value: 0)
end
end
I want to use rspec to test that this value was indeed changed within method
describe 'Something' do
let(:obj){FactoryGirl.create(:editklass)}
scenario 'Change obj value' do
ResqueKlass.perform(obj.id)
expect(obj.value).to eq(0)
end
end
This test fails where it expect 0 it get the value that was set in the factory girl.
I have also tried not using factory girl create 'obj' with let but that still does not work. I have placed bindings in the ResqueKlass perform method and i can see that the value is being updated.
PS please bear in mind that i am new to Ruby and rspec. These are not the exact classes that i am working with, the reason for that is the actual classes contain some sensitive data.
That happens, because you do not reload that record and therefore your obj still shows the old version.
Try reloading the obj with obj.reload:
describe 'Something' do
let(:obj){FactoryGirl.create(:editklass)}
scenario 'Change obj value' do
ResqueKlass.perform(obj.id)
expect(obj.reload.value).to eq(0)
end
end

Getting Ruby error "You cannot call create unless the parent is saved"

I've read through several resources but am not having any luck finding an answer. I have the following line of code, which returns "You cannot call create unless the parent is saved":
test.entries.create!({
:date => date,
:volume => 15
})
Above this line of code, I have this:
1.upto(5) do |i|
test = Test.create(
:name => "test #{i}"
)
Both of these scripts are encapsulated inside one large loop. So the script creates test and then creates an entry on test using test.entries.create!.
However, the script is failing out because the parent has not been saved. It is my understanding that .create! does .new and .save, so the initial test should be saved. I have also tried manually putting in test.save before the error with no luck. Any thoughts?
what is test in your second block of code. You mean Test.create? Also, is there any validations that might be preventing the save from happening?
Try adding create! to the test.create instead so it raises an exception if there are any errors.

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.

RSpec mocks not being called

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.

Resources