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

## はじめに

[CBcloud Advent Calendar 2020](https://qiita.com/advent-calendar/2020/cbcloud) の2日目の記事です。

本記事では、メール送信の単体テストの際、ActionMailer が依存している [Mail](https://github.com/mikel/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 に記載されているテスト方法

https://guides.rubyonrails.org/testing.html#testing-your-mailers

```ruby

require 'test_helper'
 
class UserMailerTest < ActionMailer::TestCase
  test "invite" do
    # Create the email and store it for further assertions
    email = UserMailer.create_invite('me@example.com',
                                     'friend@example.com', 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 ['me@example.com'], email.from
    assert_equal ['friend@example.com'], email.to
    assert_equal 'You have been invited by me@example.com', email.subject
    assert_equal read_fixture('invite').join, email.body.to_s
  end
end
```

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

https://relishapp.com/rspec/rspec-rails/v/3-9/docs/mailer-specs/mailer-spec

```ruby
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(["to@example.org"])
      expect(mail.from).to eq(["from@example.com"])
    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 のドキュメントに記載されたサンプルを踏襲したコードを実装しています。


```ruby
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(['to@example.org'])
        expect(mail.from).to eq(['from@example.com'])
      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('from@example.com') }
      it { is_expected.to have_sent_email.to('to@example.org') }
      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

**実行例:**
![mail_matcher_example.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1026/38a54f88-1fab-ca81-e93e-580f4493c1a2.png)

#### 余談

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


## 参考資料

- [Testing Rails Applications — Ruby on Rails Guides](https://guides.rubyonrails.org/testing.html#testing-your-mailers)
- [mailer spec - Mailer specs - RSpec Rails - RSpec - Relish](https://relishapp.com/rspec/rspec-rails/v/3-9/docs/mailer-specs/mailer-spec)
- [mikel/mail: A Really Ruby Mail Library](https://github.com/mikel/mail#using-mail-with-testing-or-specing-libraries)