Testing state_machine callbacks without touching the database

Updated . Posted . Visible to the public. Deprecated.

The information in this card is out of date. Today we would rather make a few database queries than have a fragile test full of stubs.

You should test the callback methods and its correct invocation in two separate tests. Understand the ActiveRecord note before you move on with this note.

Say this is your Spaceship class with a transition launch and a release_docking_clamps callback:

class Spaceship
  state_machine :state, :initial => :docked do
    event :launch do
      transition :docked => :en_route
    end
    before_transition :on => :launch, :do => :release_docking_clamps
  end
end

Testing that a state_machine callback is called at the correct point in a record's lifecycle is a little tricky because it isn't a regular callback chain you can invoke with run_callbacks. Besides writing a real record to the database (which you shouldn't do for performance reasons), there are several ways to work around this.

1. Use it_should_receive_callbacks

The awesome it_should_run_callbacks macro from spec_candy has been upgraded to work with state_machine callbacks:

describe Spaceship do

  describe '#launch from :docked to :en_route' do
    it_should_run_callbacks(:release_docking_clamps)
  end

  describe '#release_docking_clamps' do
    # test clamp mechanism here
  end

end

This does not currently distinguish between before_transition and after_transition.

2. Instantiate a state_machine transition

You can obtain the state machine's callback chain and run that. This is what spec_candy wraps for you (method #1).

describe Spaceship do

  describe '#launch' do
    it 'should run the proper callbacks' do
      subject.state = 'docked'
      subject.should_receive :release_docking_clamps
      transition = StateMachine::Transition.new(subject, Spaceship.state_machine, :launch, :docked, :en_route)
      transition.run_callbacks
    end
  end

  describe '#release_docking_clamps' do
    # test clamp mechanism here
  end

end

3. Use keep_invalid!

By using the keep_invalid! helper from spec_candy you can directly invoke the transition and not worry about hitting the database:

describe Spaceship do

  describe '#launch' do
    it 'should run the proper callbacks' do
      subject.state = 'docked'
      subject.should_receive :release_docking_clamps
      subject.keep_invalid!
      subject.launch
    end
  end

  describe '#release_docking_clamps' do
    # test clamp mechanism here
  end

end

This method might not work with after_transition callbacks (please investigate and report back here).

Henning Koch
Last edit
Natalie Zeumann
License
Source code in this card is licensed under the MIT License.
Posted by Henning Koch to makandra dev (2010-09-13 13:09)