Read more

Testing state_machine callbacks without touching the database

Henning Koch
September 13, 2010Software engineer at makandra GmbH

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.

Illustration web development

Do you need DevOps-experts?

Your development team has a full backlog? No time for infrastructure architecture? Our DevOps team is ready to support you!

  • We build reliable cloud solutions with Infrastructure as code
  • We are experts in security, Linux and databases
  • We support your dev team to perform
Read more Show archive.org snapshot

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
September 13, 2010Software engineer at makandra GmbH
Posted by Henning Koch to makandra dev (2010-09-13 15:09)