【Rails / RSpec】モックの基本とcredentialsのモックについて
4月からの案件ではRails / RSpecで開発しており、実務では初めてRSpecのmockを使ったので記事にしておきます。digメソッドを使った場合のcredentialsのmockも少しだけ詰まったので併せて。
RSpecでmockしたいケース
例えば以下のようなコントローラーがあるとします。
class BooksController < ApplicationController def create begin book = Book.create!(book_params) rescue => e # 何らかのエラー処理 end HogeMailer.perform_later(book) FugaMailer.perform_later(book) render status: :created end end
リクエストスペックでの成功パターンのテストケースとしては
- Bookレコードが一件増えていること
- HogeMailerが正しく動くこと
- FugaMailerが正しく動くこと
- 201が返却されること
ざっくりこの辺りでしょうか。
ここでMailerが動くことのテストをどう考えるのか、内部の動きまでテストするかどうか迷うかと思いますが、Mailerに限らず基本的にはモジュール単体でのテストを行い、内部の動きはそちらで担保する方が疎なテスト戦略としては良いのではないかと思っています。(もちろんチームによってテスト戦略は異なると思いますし、結合テストはある意味コスパが良いので、リクエストスペックだけでテストを行う戦略もあると思います)
ではリクエストスペックにおいてMailerの動きをどう扱うのか、ここで登場するのがテストでお馴染みのmockですね。
テストコード
まずMailer以外のテストコードです。
require "rails_helper" RSpec.describe "Books", type: :request do let(:body) { JSON.parse(response.body) } let!(:params) { book: attributes_for(:book) } subject { post "api/books", params: params } describe "成功" do it "Bookのレコードが1件増えている" do expect { subject }.to change { Book.count }.by(1) end it "201が返却される" do subject expect(response).to have_http_status(201) end end end
レコードが増え、201が返却されるテストはこんな感じかと思います。
そしてMailerのmockですが、まず構文は以下です。
allow(mockしたいオブジェクト).to receive(:hoge_method)
戻り値をつけたい場合
allow(mockしたいオブジェクト).to receive(:hoge_method).and_return('hogehoge')
クラスメソッドは上記でできますが、インスタンスメソッドをmockしたい場合もあるかと思います。その場合はreceive_message_chain
が使用できます。
allow(mockしたいオブジェクト).to receive_message_chain(:new, :fuga_method).and_return('fugafuga')
テストコード上では以下のようにmockを作成します。
# 略 describe "成功" do before do allow(HogeMailer).to receive(:perform_later) allow(FugaMailer).to receive(:perform_later) end # 略 end
このようにmockを作成することで、リクエストスペック上ではxxxxMailer
のperform_later
が呼び出されたこと、だけを検証することができます。
require "rails_helper" RSpec.describe "Books", type: :request do let(:body) { JSON.parse(response.body) } let!(:params) { book: attributes_for(:book) } subject { post "api/books", params: params } describe "成功" do it "Bookのレコードが1件増えている" do expect { subject }.to change { Book.count }.by(1) end it "HogeMailerのperform_laterが呼ばれる" do # 追加 subject expect(HogeMailer).to have_received(:perform_later).once end it "FugaMailerのperform_laterが呼ばれる" do # 追加 subject expect(FugaMailer).to have_received(:perform_later).once end it "201が返却される" do subject expect(response).to have_http_status(201) end end end
Railsで言うと、Jobもジョブスペックに任せて、リクエストスペックではmock化するのがいいと考えてます。もちろん自作モジュール等も。
余談ですが、テストにおけるmock化を考えられるようになってからは実装の設計において疎結合ということをよく考えられるようになったかもしれません。Everyday Railsの外から中へ進む
注釈で紹介されている「リファクタリング」はこのことかもしれないなと思ってます(ちゃんと読まなきゃ)。
credentialsのmock
最後にcredentialsのmockですが、上記Mailerと同様、credentials.yml
に記載している内容の通り書くことができます。
# hogehoge: abcd allow(Rails.application.credentials).to receive(:hogehoge).and_return('abcd')
ネストしたcredentialsの場合も、receive_message_chain
で書けると思います。
一方、digメソッドを使ったネストしたcredentialsの時に少し詰まりました。別環境では使わないのだけど、コード上どうしても読み込まれてしまうcredentialsに対して、nilを返してもらうことで読み込み時の例外発生を避けたい時に使うやつですね。
終わってみて考えればdigメソッドに対して引数を渡しているだけなので、何てことはないものでしたね…。
# hoge: # fuga: # piyo: 1234 allow(Rails.application.credentials).to receive(:dig).with(:hoge, :fuga, :piyo).and_return('1234')
終わりに
Railsを本格的に触るのは4月からでしたが、Rspecがとても簡潔に書けるので楽しいです。感覚としてはフロントエンドのテストを書いているようです。Ruby自体も、それまでに開発していたPHPと比べてシンプルで楽しいなと思います。 また経験2言語目ということで、「これ進研ゼミで見たやつだ!」を頻繁に体感しているおかげもあるかもしれません。
本で言うとrubyに関して現在はチェリー本を読んでいますが、この後はオブジェクト指向設計実践ガイドも読んでみたいですね。
参考
使えるRSpec入門・その3「ゼロからわかるモック(mock)を使ったテストの書き方」 - Qiita
Is there a way to stub Rails credential key? · Issue #2099 · rspec/rspec-rails · GitHub
Everyday Rails… Aaron Sumner 著 et al. [Leanpub PDF/iPad/Kindle]