How to test callback methods in Rails

ActiveRecord models come with useful callbacks like after_save and before_validation. Here is how to properly test your callback methods.

Our example will be a class Project. Each project has an owner and many milestones. After saving, the project creates an initial milestone and notifies the owner of its creation.

class Project

  belongs_to :owner
  has_many :milestones

  after_save :create_milestones
  after_save :notify_owner

  private

  def notify_owner
    owner.project_created!
  end

  def create_milestones
    milestones.create(:name => 'Milestone 1')
  end

end

Here is a bad example of how to test this class:

describe Model, 'after_save' do

  it 'should create an initial milestone' do
    project = Project.new
    project.milestones.should_receive(:create)
    project.run_callbacks(:after_save)
  end

  it 'should notify its owner' do
    project = Project.new(:owner => mock_model(User))
    project.owner.should_receive(:project_created!)
    project.run_callbacks(:after_save)
  end

end

The test is bad because it tests too much at once. Each test has to deal with the side effects of every other after_save method. The first example will actually crash because it calls project_created on an owner that is nil.

You would much rather test the behaviour of each callback method in isolation. Then add another test that check whether all expected methods are called.

describe Project do

  describe 'create_milestones' do
    it 'should create an initial milestone' do
      project = Project.new
      project.milestones.should_receive(:create)
      project.send(:create_milestones)
    end
  end

  describe 'notify_owner' do
    it 'should notify its owner' do
      project = Project.new(:owner => mock_model(User))
      project.owner.should_receive(:project_created!)      
      project.send(:notify_owner)
    end
  end

  describe 'after_save' do
    it 'should run the proper callbacks' do
      project = Project.new
      project.should_receive(:create_milestones)
      project.should_receive(:notify_owner)
      project.run_callbacks(:after_save)
    end
  end

end

Always try to only test one thing at a time. It will keep your examples focused and more resilient to change.

You can follow any response to this post through the Atom feed.

Avatar

Sat, 06 Mar 2010 11:26:00 GMT

by henning

Tags: