Action Mailer で覚えておくと便利な機能

Rails Console でメール送信確認

メール送信自体のテストする際に、メーラーやビューを定義しなくても ActionMailer::Base でそのまま送信できる。 SMTP サーバーの接続先を変更したり、ミドルウェアの変更に伴う問題の切り分けなどで使うと便利。

ActionMailer::Base.mail(to: 'to@example.com', from: 'from@example.com', subject: '件名', body: 'メール本文').deliver

もちろん、以下のようにActionMailer を定義してあれば、コンソール上でただ実行すれば良い。

class TestMailer < ActionMailer::Base
  default from: 'from@example.com'

  def hello
    mail(to: 'to@example.com', subject: '件名', body: 'メール本文')
  end
end

perform_deliveries オプション

perform_deliveries = false するとメール配信を抑制できる。 機能テストなどであれば、 config/environments/test.rb などの環境設定ファイルで問題ない。

ただ、特定条件下でメール送信を止めたいときは、perform_deliveries = false すると良い。 共通の親クラスを定義し、コールバックの after_action で制御すると便利。

class ApplicationMailer < ActionMailer::Base
  default from: 'from@example.com'
  layout 'mailer'

  after_action :prevent_delivery

  private
  def prevent_delivery
    message.perform_deliveries = false if @user.nil?
  end
end

コールバックのメソッド内で、自身のメールオブジェクトは message 変数で参照する。

ActionMailer のインスタンスメソッドをテストする

ActionMailer::Base.new できないので、インスタンスメソッドを直接テスト書くのが難しい。 ActionMailer の機能ではないが、 new の代わりに allocate が使える。

describe ApplicationMailer do
  describe '#prevent_delivery' do
    subject { mailer.perform_deliveries }

    # allocate でオブジェクトを取得できる
    let(:mailer) { ApplicationMailer.allocate }

    before do
      # 本来コールバックで呼ばれる部分をMock 化
      allow(mailer).to receive(:message).with(no_args).and_return(mailer)
    end

    context 'when user is present' do
      before do
        mailer.instance_variable_set(:@user, User.new)
        mailer.send(:prevent_delivery)
      end

      it { is_expected.to eq(true) }
    end

    context 'when user is nil' do
      before do
        mailer.send(:prevent_delivery)
      end

      it { is_expected.to eq(false) }
    end
  end
end

今回の例はコールバックで呼ばれる予定のインスタンスメソッドなので、Spec 内でテスト用のActionMailer クラスを定義しても良い。

let(:mailer) do
  Class.new(ApplicationMailer) do
    default(from: 'from@example.com')

    def spec
      mail(to: 'to@example.com', subject: 'subject', body: 'body')
    end
  end
end