Test whether a class level method change attribute of another class - ruby-on-rails

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

Related

Rspec passes class method but fails instance method

I'm trying to write a failing Rspec test. The actual test is associated with much longer code, but I narrowed down the problem to the class method it's testing.
Here's the test in Rspec:
context "For '.CASH.' as a stock" do
let!(:cash) { FactoryGirl.create(:stock, symbol: '.CASH.', name: 'cash', status: 'Available') }
describe "When update_stock runs on it" do
it "should still have an 'Available' status" do
# status should be 'Error' and test should fail
Stock.change_to_error
expect(cash.status).to eq('Available')
end
end
end
This is testing a model class method in Stock.rb:
def self.change_to_error
self.all.each do |stock|
stock.status = "Error"
stock.save
end
end
For some reason, this passes. However, if I changed it to use an instance method, it will fail like it should:
If stock_spec.rb changed to instance method:
context "For '.CASH.' as a stock" do
let!(:cash) { FactoryGirl.create(:stock, symbol: '.CASH.', name: 'cash', status: 'Available') }
describe "When update_stock runs on it" do
it "should still have an 'Available' status" do
# status should be 'Error' and test should fail
cash.change_to_error
expect(cash.status).to eq('Available')
end
end
end
And if stock.rb class method turned into an instance method:
def change_to_error
self.status = 'Error'
self.save
end
This would pass. Unfortunately, I have to use a class method instead of an instance method because I want to update all stocks in the DB. "Change_to_error" methods are just there to figure out the problem. Does anyone know why it passes as a class method when it should fail? But it fails correctly when it's using an instance method?
Effectively, what is happening is that the class method does not change the status attribute of 'cash', but the instance method does. I don't know why that is happening.
FYI, I'm using rspec-rails
Solution: Need to put 'cash.reload' after 'Stock.change_to_error' and before the expect line.
When using let! the object is created before the test. Updating the underlying data outside the object causes the instance to be outdated. Calling reload on it forces ActiveRecord to refresh it from the database.
When you use let, RSpec does not call the block until the first time you reference the attribute, in this case, cash. So in your first example, you're running change_to_error on no records at all and then checking the status on cash, a record that gets created on the line with expect. In your second example, the cash object is created, then changed to an error. I'd recommend tailing your log to confirm this (tail -f log/test.log)
If you change to let!, RSpec will create the object before every example is run. Another alternative is to reference cash in your example before calling change_to_error on all records that are created.

test returning value of a method in rspec

Let's say I have a dummy presenter class for my dummy model like this:
class DummyPresenter
def initialize(dummy_id)
#dummy = DummyModel.find(dummy_id)
end
def id
#dummy.id
end
def change_child_dummy_name(child_dummy_id, new_child_dummy_name)
child_dummy = #dummy.child_dummies.find(child_dummy_id)
child_dummy.update_attributes(:display_name => new_child_dummy_name)
child_dummy # I need to return a child_dummy object here!!
end
end
In my spec:
require 'spec_helper'
describe DummyPresenter do
before :all do
#dummy_presenter = DummyPresenter.new(1)
#child_dummy = DummyModel.find(1).child_dummies.first
end
it 'should update the display name of a child dummy for a dummy' do
expect(#child_dummy.display_name).to be_nil
#dummy_presenter.change_child_dummy_name(#child_dummy.id, 'Child Dummy network')
#child_dummy.reload
expect(#child_dummy.display_name).to eq('Child Dummy network')
end
it 'should return updated child dummy' do
child_dummy_id = #child_dummy.id
#dummy_presenter.should_receive(:change_child_dummy_name).at_least(:once).with(child_dummy_id, 'Child Dummy network').and_return(#child_dummy)
#dummy_presenter.change_child_dummy_name(child_dummy_id, 'Child Dummy network')
end
end
Above test cases pass without any issue.
Now, as per my understanding the first it block works perfectly fine where I just see the updated attribute. But, the second block where I expect the method: change_child_dummy_name to return #child_dummy doesn't work or maybe I didn't understand the code I've written here properly. Because, when I change change_child_dummy_name method inside presenter to this:
def change_child_dummy_name(child_dummy_id, new_child_dummy_name)
child_dummy = #dummy.child_dummies.find(child_dummy_id)
child_dummy.update_attributes(:display_name => new_child_dummy_name)
"child_dummy" # A String!! Where as I need to return a child_dummy object here!!
end
The specs again pass without raising any error. So, what am I doing wrong?
If I am not mistaken, the essence of this question is here
#dummy_presenter.should_receive(:change_child_dummy_name).at_least(:once).with(child_dummy_id, 'Child Dummy network').and_return(#child_dummy)
should_receive actually stubs the method's result.
if and_returns is used, its operand is the new value, if not the stubbed value is nil.
In your case that is the #child_dummy object. Which by the way is the reason your test passed the first time as well!
One way to bypass this behavior is to use .and_call_original which will do what you expect.
You should rewrite it as two tests:
one that tests that change_child_dummy_name is called (maybe it is not necessairy)
one that will test that the desired attributes of #child_dummy (because the object you create in your rspec test will not be the same that the method will return).

Stubbing out ActiveRecord models in Service tests

I'm following a TDD approach to building our app, and creating a whole bunch of service objects, keeping models strictly for data management.
Many of the services I've built interface with models. Take for example MakePrintsForRunner:
class MakePrintsForRunner
def initialize(runner)
#runner = runner
end
def from_run_report(run_report)
run_report.photos.each do |photo|
Print.create(photo: photo, subject: #runner)
end
end
end
I appreciate the create method could arguably be abstracted into the Print model, but let's keep it as is for now.
Now, in the spec for MakePrintsForRunner I'm keen to avoid including spec_helper, since I want my service specs to be super fast.
Instead, I stub out the Print class like this:
describe RunnerPhotos do
let(:runner) { double }
let(:photo_1) { double(id: 1) }
let(:photo_2) { double(id: 2) }
let(:run_report) { double(photos: [photo_1, photo_2]) }
before(:each) do
#service = RunnerPhotos.new(runner)
end
describe "#create_print_from_run_report(run_report)" do
before(:each) do
class Print; end
allow(Print).to receive(:create)
#service.create_print_from_run_report(run_report)
end
it "creates a print for every run report photo associating it with the runners" do
expect(Print).to have_received(:create).with(photo: photo_1, subject: runner)
expect(Print).to have_received(:create).with(photo: photo_2, subject: runner)
end
end
end
And all goes green. Perfect!
... Not so fast. When I run the whole test suite, depending on the seed order, I am now running into problems.
It appears that the class Print; end line can sometimes overwrite print.rb's definition of Print (which obviously inherits from ActiveRecord) and therefore fail a bunch of tests at various points in the suite. One example is:
NoMethodError:
undefined method 'reflect_on_association' for Print:Class
This makes for an unhappy suite.
Any advice on how to tackle this. While this is one example, there are numerous times where a service is directly referencing a model's method, and I've taken the above approach to stubbing them out. Is there a better way?
You don't have to create the Print class, simply use the one that is loaded, and stub it:
describe RunnerPhotos do
let(:runner) { double }
let(:photo_1) { double(id: 1) }
let(:photo_2) { double(id: 2) }
let(:run_report) { double(photos: [photo_1, photo_2]) }
before(:each) do
#service = RunnerPhotos.new(runner)
end
describe "#create_print_from_run_report(run_report)" do
before(:each) do
allow(Print).to receive(:create)
#service.create_print_from_run_report(run_report)
end
it "creates a print for every run report photo associating it with the runners" do
expect(Print).to have_received(:create).with(photo: photo_1, subject: runner)
expect(Print).to have_received(:create).with(photo: photo_2, subject: runner)
end
end
end
Edit
If you really need to create the class in the scope of this test alone, you can undefine it at the end of the test (from How to undefine class in Ruby?):
before(:all) do
unless Object.constants.include?(:Print)
class TempPrint; end
Print = TempPrint
end
end
after(:all) do
if Object.constants.include?(:TempPrint)
Object.send(:remove_const, :Print)
end
end
I appreciate the create method could arguably be abstracted into the Print model, but let's keep it as is for now.
Let's see what happens if we ignore this line.
Your difficulty in stubbing a class is a sign that the design is inflexible. Consider passing an already-instantiated object to either the constructor of MakePrintsForRunner or the method #from_run_report. Which to choose depends on the permanence of the object - will the configuration of printing need to change at run time? If not, pass to the constructor, if so, pass to the method.
So for our step 1:
class MakePrintsForRunner
def initialize(runner, printer)
#runner = runner
#printer = printer
end
def from_run_report(run_report)
run_report.photos.each do |photo|
#printer.print(photo: photo, subject: #runner)
end
end
end
Now it's interesting that we're passing two objects to the constructor, yet #runner is only ever passed to the #print method of #printer. This could be a sign that #runner doesn't belong here at all:
class MakePrints
def initialize(printer)
#printer = printer
end
def from_run_report(run_report)
run_report.photos.each do |photo|
#printer.print(photo)
end
end
end
We've simplified MakePrintsForRunner into MakePrints. This only takes a printer at construction time, and a report at method invocation time. The complexity of which runner to use is now the responsibility of the new 'printer' role.
Note that the printer is a role, not necessarily a single class. You can swap the implementation for different printing strategies.
Testing should now be simpler:
photo1 = double('photo')
photo2 = double('photo')
run_report = double('run report', photos: [photo1, photo2])
printer = double('printer')
action = MakePrints.new(printer)
allow(printer).to receive(:print)
action.from_run_report(run_report)
expect(printer).to have_received(:print).with(photo1)
expect(printer).to have_received(:print).with(photo2)
These changes might not suit your domain. Perhaps a runner shouldn't be attached to a printer for more than one print. In this case, perhaps you should take a different next step.
Another future refactoring might be for #from_run_report to become #from_photos, since the report isn't used for anything but gathering photos. At this point the class looks a bit anaemic, and might disappear altogether (eaching over photos and calling #print isn't too interesting).
Now, how to test a printer? Integrate with ActiveRecord. This is your adapter to the outside world, and as such should be integration tested. If all it really does is create a record, I probably wouldn't even bother testing it - it's just a wrapper around an ActiveRecord call.
Class names are just constants so you could use stub_const to stub an undefined constant and return a double.
So instead of defining a class in your before(:each) block do this:
before(:each) do
stub_const('Print', double(create: nil))
#service.create_print_from_run_report(run_report)
end

Factory_Girl not generating unique records

I'm converting my application over to use factories instead of fixtures with Factory_Girl_Rails. I have the following factory defined:
factory :requirement do
sequence(:reqTitle) {|t| "Test Requirement #{t}"}
ignore do
categoryName " "
categoryAbbr " "
end
reqText "This is a test general requirement for the purpose of, um, testing things"
status "Approved"
factory :reqWithCat do
category
end
factory :reqWithNamedCat do
category {create(:category, catName: categoryName, catAbbr: categoryAbbr)}
end
factory :reqFromUserRequirement do
user_requirement
end
end
Then, in the setup section, I run the following snippet:
(0..5).each do |x|
requirement = create(:reqWithCat)
requirement.ind_requirements {|ir| [create(:ind_requirements)]}
end
(0..5).each do |x|
create(:reqWithNamedCat, categoryName: "User Interface", categoryAbbr: "UI")
end
However, my tests are failing, apparently because records aren't being created (for instance, the index test on the requirements controller tells me that there 0 records returned when there should be 10). I run the tests in debug mode, and discover that every requirement created has the exact same id value. Also, each record has the same sequence value.
I believe the duplicate records are failing to save, which is why I'm getting a 0 return. However, I can't see what I've set up incorrectly. What am I missing here?
Once I fixed the user factories to create the related many properly, I then discovered that I had written this factory incorrectly based on the way I was defining scopes in my models. Once that was fixed, the "broken" test began working.

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.

Resources