Rails6のActionMailer機能をRSpecでテストする方法

# はじめに
皆様、こんにちは！
佐久間まゆちゃんのプロデューサーの@hiroki_tanakaです。

私は現在、Railsを使用してメール送信機能を持つシステムを構築しているのですが、
Railsの持つActionMailer機能のRSpecでのテスト実装を少し調べたので紹介させて下さい。

記事の流れとしては、最初に利用環境とActionMailerを使うための下準備に触れ、
その後に実際のActionMailer側のコードとRSpecのコードを記載致します。

# 利用環境
Ruby 2.6.6
Rails 6.0.2
RSpec 3.9.1

# 下準備
今回は[mailgun](https://www.mailgun.com/)をESP（Email Service Provider)として、メール送信を行います。
そのため、mailgunで行った設定を`development.rb`に記載をして、rails側でmailgunを使用出来るように設定をします。
ActionMailerのRSpecテストだけ行う場合は本設定は不要ですが、メール送信機能を通常のアプリケーション側から実行する場合は必要です。

```development.rb
  config.action_mailer.delivery_method = :smtp
  ActionMailer::Base.smtp_settings = {
    user_name: 'address@domain',
    password: 'password',
    domain: 'domain',
    address: 'smtp.mailgun.org',
    port: 587,
    authentication: :plain,
    enable_starttls_auto: true
  }
```

次に、mailerとspecファイルを作成します。
下記のrails generateコマンドを実行すれば、`TestMailer`クラスと紐づくSpecファイルが作成されます。

```
$ rails g mailer TestMailer
```

最後に、`config/environments/test.rb`を開き、
下記のように設定されていることを確認します。
下記のように設定されていると、ActionMailerのテストで実際にメールが送信されずにテストモードで動作します。

```test.rb
config.action_mailer.delivery_method = :test
```

# ActionMailer
Mailerクラスにはメール送信のためのコードを記載していきます。

```test_mailer.rb
class TestMailer < ApplicationMailer
  def send_mail
      mail_info = {
        to: 'hoge.from@test.com',
        from: 'fuga.to@test.com',
        from_display_name: 'ほげ商事',
        subject: 'ほげ商事の田中太郎です',
        body: '本メールはほげ商事の田中太郎からのテストメールです。'
      }

    from = Mail::Address.new mail_info['from']
    from.display_name = mail_info['from_display_name']

    mail(subject: mail_info['subject'], from: from.format, to: mail_info['to']) do |format|
      format.text { render plain: mail_info['body'] }
    end

  end
end
```

`TestMailer`は`ApplicationMailer`というActionMailerの基底クラスを継承しています。
その中にメール送信を行う`send_mail`という関数を定義します。
`mail_info`というハッシュを作成し、メール送信に必要な情報を定義します。
(`from_display_name`というのはメール受診時に送信者欄に表示される名前です。)

次に、mail gemの`Mail::Address.new`を使用して、`from`及び`display_name`をActionMailerで使用する型に変換します。
そして、最後の`mail`メソッドで`subject`(メールタイトル)・`from`(送信元)・`to`(送信先)を指定します。
`format`はHTMLメール形式かプレーンテキスト形式か指定できます。
今回はプレーンテキスト形式で送信するため`render plain`として、本文を設定しています。

以上でActionMailer側のコードは完成です。
下記のコマンド実行すれば、メール送信がされます。

```
$ rails c
[1] pry(main)> TestMailer.send_mail.deliver
```

# RSpec
`TestMailer`のテストコードを記載していきます。

```test_mailer_spec.rb
require 'rails_helper'

describe TestMailer do

  describe '#send_mail' do
    subject(:mail) do
      described_class.send_mail.deliver_now
      ActionMailer::Base.deliveries.last
    end

    context 'when send_mail' do
      it { expect(mail.from.first).to eq('hoge.from@test.com') }
      it { expect(mail.to.first).to eq('fuga.to@test.com') }
      it { expect(mail.subject).to eq('ほげ商事の田中太郎です') }
      it { expect(mail.body).to match(/本メールはほげ商事の田中太郎からのテストメールです。/) }
    end
  end
end
```

`subject(:mail)`で最初のdescribeで宣言されたクラス(今回は`TestMailer`)の`send_mail`を実行することを定義しています。
次の`ActionMailer::Base.deliveries.last`は最後に送信されたメールの情報を返却します。
そのため、この`mail`はメール送信処理を行い、値として送信したメールの情報を保持します。

context内のitでは、まずsubjectで定義した`mail`を呼び出し、メールの送信元/送信先のfrom/toをチェックしています。
`mail.to.first`や`mail.from.first`としている理由は、`mail`の戻り値内のfrom/toの情報が
`[hoge.from@test.com]`/`[fuga.to@test.com]`と配列になっているためです。
(これはメールの送信元・送信先は複数存在することがあるためです。)

その後、メールタイトルであるsubjectと本文であるbodyを比較します。
bodyは本文以外の情報(encodingなど)も含まれるため、`eq matcher`ではなく`match matcher`を使用します。

以上でRSpecの作成は完了です。コマンドラインからRSpecを実行すれば、正常にパスします。
これでActionMailerと紐づくRSpecのどちらも完成しました。

# おわりに
実装を始める前はメール送信機能やそのRSpecが難しく感じましたが、
実際に書いてみると非常に簡単かつ便利にメール送信が出来ました。

# 参考サイト
[ActionMailerのメール送信テストをRSpecで行う](https://easyramble.com/test-action-mailer-with-rspec.html)