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のコールバックでメール送信するのとか、糞コードなんだって思ってきた。