リア充爆発日記

You don't even know what ria-ju really is.

Railsでメール送信系のテストを行うには〜email-spec

会員登録時やパスワードリマインダーなどで発生するメール送信のテストはどうやってするのか調べたところ、email-specというgemが目的にぴったりっぽかった。
その名の通りRSpecとあとはMiniTestとCucumberに対応している。

email-specをRSpecに導入するメモ。
※最後に重要なお知らせがあるので必ず確認してください。

準備

Gemfile

group :development, :test do
~snip~
  gem "email_spec", "~> 1.4.0"
~snip~
end

spec_helper.rb

require 'email_spec'
~snip~
  RSpec.configure do |config|
    config.include EmailSpec::Helpers
    config.include EmailSpec::Matchers
~snip~

config.includeの部分は、以下のように使うタイミングで宣言してもいい。

describe "Signup Email" do
  include EmailSpec::Helpers
  include EmailSpec::Matchers
~snip~
end

テストを書く

users_controller_spec.rb

require "spec_helper"

describe UsersController do
  describe "POST /users" do
    it "expects to deliver the signup mail" do
      UserMailer.should_receive(:signup_confirmation)

      user = FactoryGirl.attributes_for(:user)
      post :create, user
    end
  end
end

users_controller.rb

  def create
    @user = User.new(params[:user])
    @user.status = UserStatus::TEMPORARY
    if @user.save
      UserMailer.signup_confirmation(@user).deliver
      sign_in @user
      redirect_to profile_path(@user)
    else
      render 'new'
    end
  end

ちなみにここは「予定されたメールが送信される」ことだけをテストしていて、メールの中身のテストは別途ActionMailerのテストで賄っている。
具体的には以下のとおり。

user_mailer_spec.rb

require "spec_helper"

describe UserMailer do
  let(:user) { FactoryGirl.create(:user) }
  describe "signup_confirmation" do
    let(:mail) { UserMailer.signup_confirmation(user) }

    it { expect(mail.subject).to eq(I18n.t('mail.signup_confirmation.subject', service_name: I18n.t('common.service_name'))) }
    it { expect(mail.to).to eq([user.email]) }
    it { expect(mail.from).to eq([Settings.mail.noreply]) }
    it { expect(mail.body.encoded).to include(Settings.http.root_url + confirm_email_path(token: user.email_confirmation.token))}
  end

end

user_mailer.rb

class UserMailer < ActionMailer::Base
  default from: Settings.mail.noreply

  def signup_confirmation(user)
    @user = user
    @url = Settings.http.root_url + confirm_email_path(token: user.email_confirmation.token)

    mail to: user.email, subject: I18n.t('mail.signup_confirmation.subject', service_name: I18n.t('common.service_name'))
  end
end


こんなもんでどうでしょう。

追記)
大事件です。上記のコードにはemail-specは必要なかったかもです。しかもテストコード微妙に間違ってるかもです。

追記2)
結局、メールが飛んでいることの確認は以下のようにして行いました。

models/user.rbにメール送信メソッドを作り、ユーザー作成後に呼び出すように修正

~snip~
  after_create do |user|
    user.create_email_confirmation
    user.send_email_confirm_mail
  end
~snip~
  def send_email_confirm_mail
    UserMailer.signup_confirmation(self).deliver
  end

テストは、modelのテストで賄う

describe User do

  before do
    @user = FactoryGirl.build(:user)
    ActionMailer::Base.deliveries = []
  end
~snip~
  describe "confirm_email" do
    before { @user.save }
    it "sends an e-mail" do
      expect(ActionMailer::Base.deliveries.count).to eq(1)
      expect(ActionMailer::Base.deliveries.last.to).to eq([@user.email])
    end
~snip~

キモはconfig/environments/test.rbが以下のようになっているとメールは飛ばず、ActionMailer::Base.deliveriesに配列でメールが溜まるようになるので、それで確認をするという按配。

  config.action_mailer.delivery_method = :test

ふぅ。

追記3)
最近、modelのコールバックでメール送信するのとか、糞コードなんだって思ってきた。