Search code examples
ruby-on-railsrspecpessimistic-locking

rspec change expectation fails when using active record pessimistic locking


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?


Solution

  • 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.