# Testing after_save hooks in Rails 4.2 with MiniTest

I've recently started working on a new application in Rails 4.2.0-beta2. I've been using this as an opportunity to learn more about MiniTest, with a stretch goal of being as strict with myself with testing as possible.

My general problem: How do I test the after_save action for an ActiveRecord model in such a way that I can be confident that an ActiveRecord callback will not cause my code to break?

Here's what I've got so far (gist or inline code below).

How would you (re)write these files to detect if the NotificationMailer called in the after_save hook prevented a TeamMembership from being persisted?

Note that not included here are the relevant test fixtures. You may safely assume that fixtures users(:carol) and teams(:alpha) are not associated with each other.

app/models/team_membership.rb

class TeamMembership < ActiveRecord::Base
# A proc that will enqueue NotificationMailer.team_invitation
DEFAULT_NOTIFIER = proc do |user, team|
end

class << self
# This is a class level attribute that is mainly used for testing.
# Defaults to {TeamMembership::DEFAULT_NOTIFIER}
attr_accessor :notifier
end
self.notifier = DEFAULT_NOTIFIER

belongs_to :team
belongs_to :user

after_create :invite_user_to_team!

private

# ActiveRecord callback used to equeue team invitation emails
# @return void
def invite_user_to_team!
self.class.notifier.call(team, user)
nil
end
end


tests/models/team_membership_test.rb

require 'test_helper'

class TeamMemberTest < ActiveSupport::TestCase
test 'callbacks' do
# setup
test_notifier = Minitest::Mock.new
test_notifier.expect(:call, nil, [teams(:alpha), users(:carol)])
TeamMembership.notifier = test_notifier

# test
TeamMembership.create(user_id: users(:carol).id, team_id: teams(:alpha).id)
assert test_notifier.verify

# teardown
TeamMembership.notifier = TeamMembership::DEFAULT_NOTIFIER
end
end

• Not sure I understand. after_create is called, well, after the record's been persisted. So a failure in the notification mailer shouldn't affect it. – Flambino Oct 13 '14 at 19:17
• If you were to create the model directly, then yes, that is the case. Unfortunately if I were to do something like User.first.teams << Team.first and that after_create action raised an error, the TeamMembership record does not seem to get persisted. – Damien Wilson Oct 13 '14 at 19:24
• Really? Hmm... I'll have to check that out when I have the time. (until then: I think you can just say test_notifier.verify without the assert) – Flambino Oct 13 '14 at 20:31

A valid approach would be check if NotificationMailer.team_invitation was called. In order to get this job done, you'll need first of all change this NotificationMailer.team_invitation hard-coded call to something injected. Something like this:

class TeamMembership < ActiveRecord::Base
# other methods

after_create :invite_user_to_team!

end

private
def invite_user_to_team!
nil
end
end


Now, you can create an expectation over NotificationMailer. Inside your test, you can define your mock and define its behavior:

class TeamMemberTest < ActiveSupport::TestCase
def test_after_create_new_team_member_it_should_be_notified