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. Callingreload
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 runningchange_to_error
on no records at all and then checking the status oncash
, a record that gets created on the line withexpect
. In your second example, thecash
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 referencecash
in your example before callingchange_to_error
on all records that are created.