読者です 読者をやめる 読者になる 読者になる

shared_examplesで冗長なテストファイルをすっきりすまーと

RSpecで、おんなじようなexamplesがたくさん出てきたときに、shared_exampleでまとめてしまって簡単にしたのでソースコードのdiffをメモっとく。

summary

テストする項目は、作成したメールのうち、

  • 適切な題になっているか
  • 適切な受信メールアドレスになっているか
  • 適切な送信元メールアドレスになっているか
  • bodyの内容

という感じになっていて、ここを共通化する。

無料会員と有料会員にそれぞれ登録したタイミングで送信するメールを想定してみる。

diff

# `$ git diff`
 
 RSpec.describe UserMailer, type: :mailer do
   let!(:user) { create(:user) }
 
+  # it tests `subject`, `to`, `from`, `body.encoded`
+  shared_examples "Basic email info" do
+    it "renders the subject" do
+      expect(@mail.subject).to eq subject
+    end
+    it "renders the receiver email" do
+      expect(@mail.to).to eq [user_email]
+    end
+    it "renders the sender email" do
+      expect(@mail.from).to eq [co_mail_address]
+    end
+    it "assigns @confirmation_url" do
+      expect(@mail.body.encoded).to match confirmation_url
+    end
+  end
+
   describe "#welcome_free_membership" do       
+    let!(:subject) { "無料会員登録が完了しました" }
+    let!(:user_email) { user.email }
+    let!(:co_mail_address) { "info@sample.com" }
+    let!(:confirmation_url) { root_path }
+
+    before { @mail = UserMailer.welcome_free_membership(user) }
+
       it "sends an email" do
         expect do
           mail.deliver_now
         end.to change { ActionMailer::Base.deliveries.size }.by(1)
       end
-      it "renders the subject" do
-        expect(mail.subject).to eq "会員登録が完了しました"
-      end
-      it "renders the receiver mail" do
-        expect(mail.to).to eq [user.email]
-      end
-      it "renders the sender email" do
-        expect(mail.from).to eq ["info@sample.com"]
-      end
-      it "assigns @confirmation_url" do
-        expect(mail.body.encoded).to match root_path
-      end
+
+    it_behaves_like "Basic email info"
   end
 
   describe "#welcome_monthly_membership" do
     let!(:user) { create(:user) }sample.com
+    let!(:subject) { "会員登録が完了しました" }
+    let!(:user_email) { user.email }
+    let!(:co_mail_address) { "info@sample.com" }
+    let!(:confirmation_url) { root_path }
+
+    before { @mail = UserMailer.welcome_monthly_membership(user) }
+
     describe do
       it "sends an email" do
         expect do
           mail.deliver_now
         end.to change { ActionMailer::Base.deliveries.size }.by(1)
       end
-      it "renders the subject" do
-        expect(mail.subject).to eq "会員登録が完了しました"
-      end
-      it "renders the receiver mail" do
-        expect(mail.to).to eq [user.email]
-      end
-      it "renders the sender email" do
-        expect(mail.from).to eq ["info@sample.com"]
-      end
-      it "assigns @confirmation_url" do
-        expect(mail.body.encoded).to match root_path
-      end
+
+      it_behaves_like "Basic email info"
     end
   end

2つだと+の方がまだ多いなー。でも重複して書くコードの量が減ったのと、これからmailerが増えることに恩恵にさずかれるのでベターなリファクタリングだとは思う。

めっちゃスッキリしたし、イミュータブルな値も全部変数として使いまわせるようにすることで、柔軟性も増しました。いい感じ。