環境
macOS: Big Sur Ver11.2.2
Rails: 6.0.0
Ruby: 2.6.5
rspec-rails (4.0.2)
メーラーとviewの内容
メーラーの中身
class OrderMailer < ApplicationMailer
def send_when_order(orderOrderDetail)
@orderOrderDetail = orderOrderDetail
visit_day_id = @orderOrderDetail.visit_day_id # 1とかが代入される
@visit_day = VisitDay.find(visit_day_id).name # アクティブハッシュのモデルからidで探し、そのidに該当するnameキーの値を取得
visit_time_id = @orderOrderDetail.visit_time_id
@visit_time = VisitTime.find(visit_time_id).name
menu_id = @orderOrderDetail.menu_id # インスタンスに入っているparamsを使ってmenu_idを特定
@menu = Menu.find(menu_id) # 特定したmenu_idを使って注文があったメニューを特定
@user = @menu.user # 注文があったメニューを投稿したお店を特定
mail(to: @user.email, subject: '注文のお知らせ')
end
end
メール本文の内容
html形式(よくある感じです)
<h1>注文のお知らせ</h1>
<h2><%= @user.shop_name %> 様、以下のとおり注文がありました!</h2>
<h2>お料理のご準備をお願いします!</h2>
<h3>【注文内容】</h3>
<table>
<tr>
<th>
<p>メニュー名</p>
</th>
<td>
<p><%= @menu.name %></p>
</td>
</tr>
<tr>
<th>
<p>注文数</p>
</th>
<td>
<p><%= @orderOrderDetail.quantity %></p>
</td>
</tr>
<tr>
<th>
<p>合計金額(税込)</p>
</th>
<td>
<p><%= @orderOrderDetail.total_price %>円</p>
</td>
</tr>
<tr>
<th>
<p>ご注文者様氏名</p>
</th>
<td>
<p><%= @orderOrderDetail.last_name %><%= @orderOrderDetail.first_name %></p>
</td>
</tr>
<tr>
<th>
<p>ご注文者様氏名(フリガナ)</p>
</th>
<td>
<p><%= @orderOrderDetail.last_name_kana %><%= @orderOrderDetail.first_name_kana %></p>
</td>
</tr>
<tr>
<th>
<p>連絡先</p>
</th>
<td>
<p><%= @orderOrderDetail.phone_number %></p>
</td>
</tr>
<tr>
<th>
<p>来店時間</p>
</th>
<td>
<p><%= @visit_day %>の<%= @visit_time %></p>
</td>
</tr>
</table>
text形式(特段変わったところはない)
◆注文のお知らせ◆
<%= @user.shop_name %> 様、以下のとおり注文がありました!
お料理のご準備をお願いします!
【注文内容】
メニュー名: <%= @menu.name %>
注文数: <%= @orderOrderDetail.quantity %>
合計金額(税込): <%= @orderOrderDetail.total_price %>円
ご注文者様氏名: <%= @orderOrderDetail.last_name %><%= @orderOrderDetail.first_name %>
ご注文者様氏名(フリガナ): <%= @orderOrderDetail.last_name_kana %><%= @orderOrderDetail.first_name_kana %>
連絡先: <%= @orderOrderDetail.phone_number %>
来店時間: <%= @visit_day %>の<%= @visit_time %>
テスト内容とエラー内容
テストでは、上記のメール本文(text, html形式)に「注文のお知らせ」というテキストが表示されているか確かめるための記述をしました。
以下の2つ目のテスト内容です。
require 'rails_helper'
# メールの単体テストでは①メールを送信され、作成されていること、②メールの中身が適切であることの2つをテストする。
RSpec.describe OrderMailer, type: :mailer do
describe 'send_when_orderメソッドで送るメールの検証' do
let(:user) { FactoryBot.create(:user) }
let(:menu) { FactoryBot.create(:menu, user_id: user.id) }
let(:orderOrderDetail) { FactoryBot.build(:order_order_detail, menu_id: menu.id) }
let(:mail) { OrderMailer.send_when_order(orderOrderDetail) }
it 'メールのヘッダー情報が正しいか' do
expect(mail.subject).to eq('注文のお知らせ')
expect(mail.to).to eq [menu.user.email]
expect(mail.from).to eq(['noreply@example.com'])
end
it 'メール本文が正しいか' do
mail.deliver_now
last_mail = ActionMailer::Base.deliveries.last
expect(last_mail.body).to match("注文のお知らせ")
expect(last_mail.body).to match("注文のお知らせ")
end
it 'メールを実際に送り、作成できているか' do
expect{
mail.deliver_now
}.to change{ ActionMailer::Base.deliveries.size }.by(1)
end
end
end
実行したところ、以下のエラーが発生。
body というメソッドはないと怒られました。
1) OrderMailer send_when_orderメソッドで送るメールの検証 メール本文が正しいか
Failure/Error: expect(last_mail.body).to match("注文のお知らせ")
NoMethodError:
undefined method `body' for nil:NilClass
# ./spec/mailers/order_spec.rb:21:in `block (3 levels) in <top (required)>'
そこで、該当のテスト部分を以下のように修正(抜粋)。
it 'メール本文が正しいか' do
mail.deliver_now
last_mail = ActionMailer::Base.deliveries.last
expect(last_mail.body.to_s).to match("注文のお知らせ")
expect(last_mail.body.to_s).to match("注文のお知らせ")
end
すると、今度は以下のエラーが発生(長いので抜粋)。
ちょっとよくわかりませんが、「.to_s」メソッドの変換が良くなかったのだと思います。
1) OrderMailer send_when_orderメソッドで送るメールの検証 メール本文が正しいか
Failure/Error: expect(last_mail.to_s).to match("注文のお知らせ")
expected "Date: Fri, 12 Mar 2021 12:38:51 +0900\r\nFrom: noreply@example.com\r\nTo: lynwood@yahoo.com\r\nMessa...NCiAg\r\nPC9ib2R5Pg0KPC9odG1sPg0K\r\n\r\n----==_mimepart_604ae24b4f9cc_10a4b3fcedf03204418360--\r\n" to match "注文のお知らせ"
Diff:
@@ -1,64 +1,127 @@
-注文のお知らせ
(以下、省略)
解決策
テストコードを以下のように記述したところ、無事にテストが通りました!
it 'メール本文が正しいか' do
mail.deliver_now
last_mail = ActionMailer::Base.deliveries.last
expect(last_mail.html_part.body.to_s).to match("注文のお知らせ")
expect(last_mail.text_part.body.to_s).to match("注文のお知らせ")
end
今回のようにhtml形式とtext形式の2つが用意されている場合は、上記のように記述することができるみたいです。
(追記)
Railsガイドを見ていたら、このやり方が普通に紹介されていてショックを受けました、、
該当のページ
(追記ここまで)
メール本文を文字列に変換する必要があるということは、メールはデフォルトで文字列以外にエンコードされているはず。調べてみると、Railsガイドにちゃんと記載がありました!
ただし、単純に変換してもうまくいかないことがあるんだなーという印象です。
以上!
参考サイト
stackoverflowの以下の記事が最高だった!
rspec-email-本文テキストを取得する方法は?
Railsで、テストでのみメール本文が空になるのはなぜですか?