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.
Related
as i try to check internally my method was calling or not in rspec but it got the following errors
context "#Meeting_schedule" do
let(:meeting_schedule) { FactoryGirl.create(:meeting_schedule,:time=>"morning",:schedule_name=>"planned_meet", :schedule_info=>[{ "from"=>"00:00", "to"=>"00:01"}]) }
it "if the same schedule was created again dont save it again" do
schedule.save
params = {:time=>"morning",:schedule_name=>"planned_meet", :schedule_info=>[{ "from"=>"00:00", "to"=>"00:01"}]}
meeting_schedule.create_or_update_meeting_schedule(params)
expect(meeting_schedule).to receive(:updating_the_user)
end
end
i got the following error
Failure/Error: expect(meeting_schedule.create_or_update_meeting_schedule(params)).to receive(:updating_the_user)
(#<Meeting_schedule:0x0055dbaf0da710>).updating_the_user(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
# ./spec/models/meeting_schedule_spec.rb:122:in `block (4 levels)
so what was wrong in my code?
my method
def create_or_update_meeting_schedule(params)
self.attributes = params
if self.changed and self.save
updating_the_user
end
self
end
can anyone help me out
Mocks must always be setup before the method under test is called as there is no reliable way to test if a normal method was called in Ruby. There are two ways of doing this in RSpec.
The first is using expect(...).to receive(...) which must be done before the method is called - this detaches the method and replaces it with a mock that wraps the original method.
The test will fail if the method is not called in the example.
The second is by using spies. You can either replace an entire object with a spy:
RSpec.describe "have_received" do
it "passes when the message has been received" do
invitation = spy('invitation')
invitation.deliver
expect(invitation).to have_received(:deliver)
end
end
This "spy object" will keep track of any method you call on it.
You can also spy on a single method:
class Invitation
def self.deliver; end
end
RSpec.describe "have_received" do
it "passes when the expectation is met" do
allow(Invitation).to receive(:deliver)
Invitation.deliver
expect(Invitation).to have_received(:deliver)
end
end
Spies are very useful in the case when you want to mock the method in the test setup - for example in the before block.
class Invitation
def self.deliver; end
end
RSpec.describe "have_received" do
before do
allow(Invitation).to receive(:deliver)
end
it "passes when the expectation is met" do
Invitation.deliver
expect(Invitation).to have_received(:deliver)
end
end
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
I have a situation where I stub the .new method of a class, but that makes it to return nil objects, and later those objects are needed, and I am not sure how to deal with it. Here is my rspec code:
describe ShopWorker do
describe '#perform' do
let(:worker) { ShopWorker.new }
it 'creates a new instance of Shopper' do
user = FactoryGirl.create(:user)
expect(Shopper).to receive(:new).with(user)
worker.perform(user.id)
end
end
end
And here is my Worker code:
class ShopWorker
include Sidekiq::Worker
def perform(user_id)
user = User.find(user_id)
shopper = Shopper.new(user)
shopper.start # This fails because Shopper.new returns NIL
end
end
So, since I am stubbing the new method with expect(Shopper).to receive(:new).with(user), then when in the worker it does shopper.start, that is nil, and therefore it breaks. How should I solve this? Ideally, I would like to test that a new instance of Shopper is done and also that the method start is called for that instance.
There are a couple of things you can do:
Expect to receive :new but provide a return value (possibly a mock), using and_return(). The problem is that to receive has an implicit and_return(nil) unless you provide a return value explicitly.
Don't stub :new, let it do its job and expect :start on any Shopper instance: expect_any_instance_of(Shopper).to receive(:start).
Ask yourself what value this test provides. The test knows a lot about the implementation, to a point where you always have to change both. What is the impact of shopper.start? Can you assert anything about the actual business value?
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
I am learning how to write tests so I'm writing one for a small method that I wrote. I want to call this method, update_user_region_id, and I should be expecting a change in the user object.
def update_user_region_id(user_id, region_id)
user = User.where(id: user_id).first
region = Region.where(id: region_id).first
return if user.nil?
user.region_id = region_id
user.region = region.name
user.save!
end
update_user_region_id is a method called from another method (not sure if this is relevant).
For my test, I've written:
it 'should update user region id' do
user = Factory :user
city = Factory :city
expect{
update_user_region_id(user.id, city.id)
}.to change(user, :region_id)
end
I keep getting the following error:
undefined method `update_user_region_id' for #<RSpec::Core::ExampleGroup::Nested_1:0x007f9e0a57f490>
1. Is this a good test?
2. How do I fix this test?
Thanks.
The problem is that in the context in which you call it, self refers to an object of class RSpec::Core::ExampleGroup::Nested_1, and since you don't provide an explicit receiver, it's that self object that receives the message.
As #pjam notes, without more information it's impossible to explain how exactly to fix your test. But the general point is that the update_user_region_id message needs an appropriate receiver. If, for example, that were a class method on the Foo class, you could call Foo.update_user_region_id. Or if it's an instance method, you may need to instantiate an object first (e.g., Foo.new.update_user_region_id).