1
\$\begingroup\$

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|
 NotificationMailer.team_invitation(team, user).deliver_later
 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
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Oct 13, 2014 at 17:45
\$\endgroup\$
3
  • \$\begingroup\$ 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. \$\endgroup\$ Commented Oct 13, 2014 at 19:17
  • \$\begingroup\$ 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. \$\endgroup\$ Commented Oct 13, 2014 at 19:24
  • \$\begingroup\$ 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) \$\endgroup\$ Commented Oct 13, 2014 at 20:31

1 Answer 1

2
\$\begingroup\$

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!
 def notification_method(notification_service = NotificationMailer)
 @notification = notification_service
 end
 private
 def invite_user_to_team!
 @notification.team_invitation(team, user)
 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
 notification = Minitest::Mock.new
 notification.expects(:team_invitation).with(team, user).once
 # your TeamMember class working to create new
 end
end

I hope it helps :)

answered Nov 17, 2014 at 16:58
\$\endgroup\$
1
  • \$\begingroup\$ This is exactly the kind of feedback I was hoping for, thanks! I particularly like the way you default to the production use-case as a default method argument. This seems much cleaner and reads much more simply than the lambda constant I was using in my question. \$\endgroup\$ Commented Nov 20, 2014 at 0:19

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.