3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

RSpecでメーラーのテスト実行時にメールの本文がなぜか空になっている問題を解決する

Last updated at Posted at 2021-03-12

環境

macOS: Big Sur Ver11.2.2
Rails: 6.0.0
Ruby: 2.6.5
rspec-rails (4.0.2)

メーラーとviewの内容

メーラーの中身
app/mailers/order_mailer.rb
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形式(よくある感じです)

app/views/order_mailer/send_when_order.html.erb
<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形式(特段変わったところはない)

app/views/order_mailer/send_when_order.text.erb
◆注文のお知らせ◆
<%= @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つ目のテスト内容です。

spec/mailers/order_spec.rb
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)>'

そこで、該当のテスト部分を以下のように修正(抜粋)。

spec/mailers/order_spec.rb
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 @@
       -注文のお知らせ
(以下、省略)

解決策

テストコードを以下のように記述したところ、無事にテストが通りました!

spec/mailers/order_spec.rb
    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で、テストでのみメール本文が空になるのはなぜですか?

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?