はじめに
WebMockは昔からある有名なモックライブラリですが、RSpecでちょっとした細かい検証を行おうとした際にいろいろ試したことを自分用メモとしてまとめます。
主に外部API等へのリクエストを想定した、HTTPリクエストに対する検証の方法を取り扱っています。
環境
Ruby : 2.7.1
RSpec : 3.9.0
webmock : 3.8.3
テスト対象のコード
サンプルとして今回は、https://jsonplaceholder.typicode.com/ へリクエストします。
require 'net/http'
class Sample
def request(params = {})
URI.parse('https://jsonplaceholder.typicode.com/todos')
.tap { |uri| uri.query = URI.encode_www_form(params) }
.then { |uri| Net::HTTP.get_response(uri) }
.then { |res| res.body if res.is_a?(Net::HTTPSuccess) }
end
end
基本形
webmockのstub_request を使用して、指定のURLへのリクエストをmockにします。
またexpect 時の検証には a_request を使うパターンで行っています。
context '基本形' do
before do
stub_request(:get, 'https://jsonplaceholder.typicode.com/todos').and_return(status: 200, body: 'hoge')
end
it '正しくリクエストされること' do
expect(Sample.new.request).to eq 'hoge'
expect(a_request(:get, 'https://jsonplaceholder.typicode.com/todos')).to have_been_made.once
end
end
context '基本形その2 クエリストリングも検証' do
before do
stub_request(:get, 'https://jsonplaceholder.typicode.com/todos?userId=2').and_return(status: 200, body: 'hoge')
end
it '正しくリクエストされること' do
expect(Sample.new.request(userId: 2)).to eq 'hoge'
expect(
a_request(:get, 'https://jsonplaceholder.typicode.com/todos').with(query: { userId: 2 })
).to have_been_made.once
end
end
-
have_bee_madeはWebMockが用意しているRSpec用のマッチャーです。他にもいくつかのマッチャーが用意されています。 -
onceも同じくWebMockが用意しているマッチャーで、こちらもいろいろなバリエーションがあります。 - https://github.com/bblimke/webmock/blob/master/lib/webmock/rspec/matchers/webmock_matcher.rb
-
withを使用することで明示的にクエリストリングを検証できます。-
withを使用しなくともa_request(:get, 'https://jsonplaceholder.typicode.com/todos?userId=2')というように愚直に書いてもOKです。 - クエリストリングの他に
withでは、bodyheadersbasic_authという値を使用した検証も可能です。
-
正規表現でワイルドカード的なやつを使いたい
例えば、リクエストするURLをtodosからtodos/1に変えたい場合や、もっと複数のクエリストリングを指定したりしてテストしたい場合があると思いますが、その場合、毎回リクエストに合わせてstub_requestのURL文字列を変更しないとmockされません。それだと面倒なので、正規表現を使うことで対応します。
stub_request(:get, /https:\/\/jsonplaceholder.typicode.com/).and_return(status: 200, body: 'hoge')
こうすることで、https://jsonplaceholder.typicode.com/ ドメイン配下のリクエストはすべてmockされて、a_requestで検証可能になります。
また、正規表現はa_request でも使用することが可能です。
expect(a_request(:get, /https:\/\/jsonplaceholder.typicode.com/)).to have_been_made
ただし、以下のようにa_request でwithを使用したクエリストリングの検証はできません。
# この書き方はNG
expect(a_request(:get, /https:\/\/jsonplaceholder.typicode.com/).with(query: { userId: 2)).to have_been_made
Arrayなクエリストリングの検証方法
以下のような同一のキーを複数指定したクエリストリングの検証をしたい場合の方法を説明します。
Railsデフォルトな場合
Railsなアプリケーションの場合、以下のように[]を付けるのがデフォルトです。([]はエンコードされるので正確には%5B%5D)
?userId[]=1&userId[]=2&userId[]=3
以下のようにwith にquery のhashで配列を指定すればOKです。railsっぽく書けます。
expect(
a_request(:get, 'https://jsonplaceholder.typicode.com/todos').with(query: { userID: [1, 2, 3] })
).to have_been_made
Railsデフォルトじゃない場合
外部APIなどRails以外のアプリケーションの場合、以下のように[]を付けないパターンがほとんどです。
?userId=1&userId=2&userId=3
そういうときは、以下のように:flat_array というsymbolをWebMockに設定します。
WebMock::Config.instance.query_values_notation = :flat_array
検証方法は以下のようにwith のquery hashで値を文字列で指定します。
expect(
a_request(:get, 'https://jsonplaceholder.typicode.com/todos').with(query: 'userID=1&userID=2&userID=3')
).to have_been_made
文字列を直接書くのが嫌な場合は、URI.encode_www_form などを使用して文字列に変換すると良いでしょう。
expect(
a_request(:get, 'https://jsonplaceholder.typicode.com/todos').with(query: URI.encode_www_form(userId: [1, 2, 3]))
).to have_been_made
flat_arrayを設定した場合はwithでクエリストリングがソートされる
WebMock::Config.instance.query_values_notation = :flat_array を設定した場合ですが、withでクエリストリングを検証するときはキーを文字順でソートした値を渡す必要があります。
表題のテスト対象のコードで以下を実行するとクエリストリングは userId=1&aa=1とリクエストされますが、 withのqueryには並び替えた文字列aa=1&userId=1を渡さないとダメでした。
WebMock::Config.instance.query_values_notation = :flat_array
Sample.new.request(userId: 1, aa: 1)
# OK
expect(
a_request(:get, 'https://jsonplaceholder.typicode.com/todos').with(query: 'aa=1&userId=1')
).to have_been_made
# hashはOK
expect(
a_request(:get, 'https://jsonplaceholder.typicode.com/todos').with(query: { userId: 1, aa: 1 })
).to have_been_made
# これはNG
expect(
a_request(:get, 'https://jsonplaceholder.typicode.com/todos').with(query: 'userId=1&aa=1')
).to have_been_made
参考