【Rails】ActionMailer のテストで Mail::Matchers を使う


はじめに

CBcloud Advent Calendar 2020 の2日目の記事です。

本記事では、メール送信の単体テストの際、ActionMailer が依存している Mail に含まれている Mail::Matchers を RSpec のマッチャとして利用する方法を紹介します。

また、比較対象として、以下の2つも同時に記載します。

  1. Rails Guides に記載されているテスト方法
  2. RSpec のドキュメントに記載されているテスト方法

最後に、Mail::Matchers を使った例の紹介と、実際に動くサンプルコードを添付します。

動作確認環境

  • ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin19]
  • actionmailer (6.0.3.4)
  • rspec (3.10.0)

テストコード

Rails Guides に記載されているテスト方法


require 'test_helper'

class UserMailerTest < ActionMailer::TestCase
  test "invite" do
    # Create the email and store it for further assertions
    email = UserMailer.create_invite('[email protected]',
                                     '[email protected]', Time.now)

    # Send the email, then test that it got queued
    assert_emails 1 do
      email.deliver_now
    end

    # Test the body of the sent email contains what we expect it to
    assert_equal ['[email protected]'], email.from
    assert_equal ['[email protected]'], email.to
    assert_equal 'You have been invited by [email protected]', email.subject
    assert_equal read_fixture('invite').join, email.body.to_s
  end
end

RSpec のドキュメントに記載されているテスト方法

require "rails_helper"

RSpec.describe NotificationsMailer, :type => :mailer do
  describe "notify" do
    let(:mail) { NotificationsMailer.signup }

    it "renders the headers" do
      expect(mail.subject).to eq("Signup")
      expect(mail.to).to eq(["[email protected]"])
      expect(mail.from).to eq(["[email protected]"])
    end

    it "renders the body" do
      expect(mail.body.encoded).to match("Hi")
    end
  end
end

Mail::Matchers を使ったテスト方法

ActionMailer の依存で Mail gem は既にインストール済みなので、導入手順は簡単です。

  • RSpec の設定で、 Mail::Matchers を include する
  • ActionMailer のテストで Mail::Matchers が提供するマッチャを使う

以下が実際に Mail::Matchers を使って書いたコードです。
比較のため、最初のコンテキストでは先述した RSpec のドキュメントに記載されたサンプルを踏襲したコードを実装しています。

RSpec.configure do |config|
  config.include Mail::Matchers, type: :mailer
end

RSpec.describe NotificationsMailer, type: :mailer do
  before do
    ActionMailer::Base.deliveries.clear
  end

  describe '#signup' do
    # @see: https://relishapp.com/rspec/rspec-rails/v/3-9/docs/mailer-specs/mailer-spec
    context 'when using RSpec mailer examples' do
      subject(:mail) { described_class.signup }

      it 'renders the headers' do
        expect(mail.subject).to eq('Signup')
        expect(mail.to).to eq(['[email protected]'])
        expect(mail.from).to eq(['[email protected]'])
      end

      it 'renders the body' do
        expect(mail.body.encoded).to match('Hi')
      end

      it 'sends the mail' do
        expect { mail.deliver_now }.to change { ActionMailer::Base.deliveries.count }
      end
    end

    # @see: https://github.com/mikel/mail#using-mail-with-testing-or-specing-libraries
    context 'when using Mail::Matchers' do
      subject(:mail) { described_class.signup.deliver_now }

      it { is_expected.to have_sent_email }
      it { is_expected.to have_sent_email.from('[email protected]') }
      it { is_expected.to have_sent_email.to('[email protected]') }
      it { is_expected.to have_sent_email.with_subject('Signup') }
      it { is_expected.to have_sent_email.with_body('Hi') }
    end
  end
end

メール送信後のマッチャが全て have_sent_email に集約されているのがわかりますね。
ここで紹介したもの以外にも、添付ファイルに対するマッチャなど、メールのテストに必要なものが一通り揃っているので、ぜひドキュメントを確認して使ってみてください。
https://github.com/mikel/mail#using-mail-with-testing-or-specing-libraries

最後に

ここまで読んで、実際に自分で試してみたいと思われた方のために、動くサンプルコードを Gist で公開しています。
https://gist.github.com/tomohiro/dfa7362cd066dd87f25f40bdd6856513

実行例:

余談

個人的には --format documentation で出力したときの表現が冗長に感じています。

参考資料