The blog of , a Ruby on Rails development team

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.

Growing Rails Applications in Practice
Check out our new e-book:
Learn to structure large Ruby on Rails codebases with the tools you already know and love.

Recent posts

Our address:
makandra GmbH
Werner-von-Siemens-Str. 6
86159 Augsburg
Germany
Contact us:
+49 821 58866 180
info@makandra.de
Commercial register court:
Augsburg Municipal Court
Register number:
HRB 24202
Sales tax identification number:
DE243555898
Chief executive officers:
Henning Koch
Thomas Eisenbarth