Testing async ActionMailer jobs with ActiveJob in Minitest

One of the fantastic new libraries that came with Rails 4.2 is ActiveJob - a standard interface to background queues like Sidekiq, Resque or Delayed Job. It lets you write your jobs using the ActiveJob syntax and decide on a background queue later. And if - a year from now - you are not happy with your choice of background queue anymore, you can switch it out without having to rewrite any of the worker code.

But the real power of ActiveJob is that it abstracts the implementation details of the different queuing backends and lets you develop additional tools and libraries around that central API. Any gem can now safely enqueue background jobs for example, without having to know what queue backend the application uses.

An important aspect of background jobs is the ability to test them of course. You want to make sure that a certain piece of code really enqueues a job, and that its the right job with the right parameters!

Let's test this!

Since I started using Minitest for all my projecs (at least for the ones for which I was allowed to make that decision), it wasn't long before I had to test whether a specific ActionMailer job had been enqueued properly. And, lo and behold, it seems that there is no simple Minitest matcher to do so.

This is why I had to come up with my own helpers which I want to share here:

# test/support/action_mailer_helpers.rb
module ActionMailerHelpers
  include ActiveJob::TestHelper

  def must_enqueue_action_mailer_job(mailer_class, mailer_method, *args)
    must_enqueue_jobs(1) { yield }
    action_mailer_job_must_be_enqueued(mailer_class, mailer_method, *args)

  # Define `action_mailer_job_must_be_enqueued`
  #    and `action_mailer_job_wont_be_enqueued`
  %w(must wont).each do |verb|
    define_method "action_mailer_job_#{verb}_be_enqueued" do |mailer_class, mailer_method, *args|
      _(enqueued_jobs).public_send "#{verb}_include",
                                   action_mailer_job(mailer_class, mailer_method, *args)


  def action_mailer_job(mailer_class, mailer_method, *args)
      job: ActionMailer::DeliveryJob,
      args: [
      queue: 'mailers'

# test/test_helper.rb
require 'support/action_mailer_helpers'

class ActiveSupport::TestCase
  include ActionMailerHelpers

Note that I'm using Minitest::Spec instead of the regular Minitest::Unit. The code can be easily changed to work with Minitest::Unit though - use assert_enqueued_job instead of must_enqueue_jobs, assert_includes enqueued_jobs, ... instead of _(enqueued_jobs).must_include(...) etc.

Some comments

  • Include these helpers in the appropriate class in your test_helper.rb. I'm using minitest-rails, so in my case they go into ActiveSupport::TestCase.

  • I'm using a bit of metaprogramming to define action_mailer_job_must_be_enqueued and action_mailer_job_wont_be_enqueued. Both methods just differ in that the one uses must_include and the other wont_include.

  • ActiveJob::TestHelper contains the matchers that let you access the ActiveJob job queue. It has to be included explicitly, otherwise must_enqueue_jobs and enqueued_jobs won't work.

  • The helpers assume you haven't changed the name of the queue that your ActionMailer jobs are enqueued to. If you did, change the value in the action_mailer_job method from mailers to whatever you're using.


Use must_enqueue_action_mailer_job with a block to verify that exactly one job is enqueued by that block and that it is a ActionMailer job with the expected parameters:

# Assuming you're testing your password reset form using Capybara
must_enqueue_action_mailer_job UserMailer, 'password_reset_instructions', user do
  click_button 'Send password reset instructions'

Use action_mailer_job_must_be_enqueued and action_mailer_job_wont_be_enqueued to test that some code before has (or hasn't) enqueued a ActionMailer job with the expected parameters. These methods won't test how many jobs are enqueued, only that your ActionMailer job is (or isn't) one of them.

# Just for demo purposes, you shouldn't send mails from model callbacks of course!
user = User.create!(...)
action_mailer_job_must_be_enqueued UserMailer, 'welcome', user

That's all, folks!

Discuss this post on Hacker News

Ideas? Constructive criticism? Think I'm stupid? Let me know in the comments!