いまさらRSpecに少し慣れてテストをすっきり書く方法について少し覚えた話
いまさらRSpecをさわってきて慣れて覚えた話もメモしておきます。
TL;DR
- letで宣言した変数の初期化用パラメータで置き換えたくなったパラメータもletで宣言しておくとよさそう
- shared_context や shared_examplesでまとめるのがちょうどいいくらいの事例としてちょうどいいのは、外部公開するAPIの異常系のレスポンスのテストくらいでは
慣れてきて覚えた話
letで宣言した変数の初期化用パラメータで置き換えたくなったパラメータもletで宣言しておく
これは同僚の方から教えていただいていいな、と思ったんですが、
describe "post comment" do let(:params) { { user_name: "HOGE", comment: "...", "book_id": 3 } } context "valid request" do it "can be posted" do ... end end context "invalid user" do let(:params) { { user_name: "", comment: "...", "book_id": 3 } } it "cannot be posted" do ... end end context "invalid comment" do let(:params) { { user_name: "HOGE", comment: "INVALID_FORMAT", "book_id": 3 } } it "cannot be posted" do ... end end context "invalid book" do let(:params) { { user_name: "HOGE", comment: "...", "book_id": nil } } it "cannot be posted" do ... end end end
のような場合、 params
の値の一部がそれぞれの context
で異なるので、それぞれの context
ブロックでパラメータを宣言しなおしていますが、
describe "post comment" do let(:user_name) { "HOGE" } let(:comment) { "..." } let(:book_id) { 3 } let(:params) { { user_name: "HOGE", comment: "...", "book_id": 3 } } context "valid request" do it "can be posted" do ... end end context "invalid user" do let(:user_name) { "" } it "cannot be posted" do ... end end context "invalid comment" do let(:comment) { "INVALID_FORMAT" } it "cannot be posted" do ... end end context "invalid book" do let(:book_id) { nil } it "cannot be posted" do ... end end end
のように、example内で触る変数はparamsだけかもしれないんですが、それぞれのcontextで注目しているパラメータだけあとで置き換えられるように、宣言の時に工夫しておくとすっきりするなと思いました。
shared_context や shared_examplesでまとめるのがちょうどいいくらいの事例としての外部公開するAPIの異常系のレスポンスのテスト
この話がしたくてこの記事を書きはじめたのに随分長くなってしまったのですが、RSpecでは、shared_context
を使ってspecの中で繰り返し登場するテストケースを
describe "post comment" do let(:user_name) { "HOGE" } let(:comment) { "..." } let(:book_id) { 3 } let(:params) { { user_name: "HOGE", comment: "...", "book_id": 3 } } context "valid request" do it "can be posted" do ... end end context "invalid user" do let(:user_name) { "" } it "cannot be posted" do ... expect(:status).to eq 400 expect(:body).to eq "Invalid Request" # このケース自体がいいかは微妙ですが... end end context "invalid comment" do let(:comment) { "INVALID_FORMAT" } it "cannot be posted" do ... expect(:status).to eq 400 expect(:body).to eq "Invalid Request" end end context "invalid book" do let(:book_id) { nil } it "cannot be posted" do ... expect(:status).to eq 400 expect(:body).to eq "Invalid Request" end end end
のような場合、
describe "post comment" do let(:user_name) { "HOGE" } let(:comment) { "..." } let(:book_id) { 3 } let(:params) { { user_name: "HOGE", comment: "...", "book_id": 3 } } context "valid request" do it "can be posted" do ... end end shared_examples "invalid request" do it "returns 400 response" do expect(:status).to eq 400 expect(:body).to eq "Invalid Request" end end context "invalid user" do let(:user_name) { "" } it "cannot be posted" do ... it_behaves_like "invalid request" end end context "invalid comment" do let(:comment) { "INVALID_FORMAT" } it "cannot be posted" do ... it_behaves_like "invalid request" end end context "invalid book" do let(:book_id) { nil } it "cannot be posted" do ... it_behaves_like "invalid request" end end end
のように、同じテストコードを実行している部分を shared_examples
に定義して it_behaves_like "SHARED_EXAMPLE_NAME"
で呼び出すことができます。
こうするとテストケースはDRYになりますが、
- 一方でテストが読みにくくなったり
- バリデーションエラーなどは案外全く同じコードにならなかったり
- たとえば、上記のサンプルコードは実際はパラメータごとに
invalid user
などのレスポンスを返したほうが親切でしょう
- たとえば、上記のサンプルコードは実際はパラメータごとに
して、使いどころが難しいんですが、外部公開しているAPIの場合、
という感じで使ってみるとよいかもしれないな、と思ったのでメモです。
参考
https://qiita.com/jnchito/items/42193d066bd61c740612 https://qiita.com/etet-etet/items/7babe4856a1cd62b9ecb