I have a Rails 4.2.0
method that uses pessimistic locking to change a counter
class Foo < < ActiveRecord::Base
def bump!
transaction do
lock!
parent.lock!
lock.counter += 1
parent.counter += 1
save!
parent.save!
end
end
end
I am using Rspec 3.1
to test it like so
expect{foo.bump!}.to change(foo, :counter).by(1)
expect{foo.bump!}.to change(foo.parent, :counter).by(1)
The first change(foo, :counter)
test passes but the second change(foo.parent, :counter)
fails unless I comment out both lock!
and parent.lock!
If I rewrite the failing test like this, it passes
prev_counter = foo.parent.counter
foo.bump!
expect(foo.parent.counter).to eq prev_counter + 1
Why doesn't it work with expect{...}.to change
?
Your issue is that the instance of foo.parent
in your RSpec test is not the same instance of parent
that your Foo#bump!
method is modifying, because calling parent.lock!
reloads the parent association to get the lock and thus you modify a different instance than rspec has bound its own lambda to. The simplest fix is to use the change { }
syntax which doesn't bind the receiver instance to foo.parent
but instead just to foo
, which doesn't change, like so:
expect{foo.bump!}.to change{foo.counter}.by(1)
expect{foo.bump!}.to change{foo.parent.counter}.by(1)
This fix worked locally for me.