【RSpec】Webmockを使ってテストをモックする
TL;DR
webmockを使って外部APIからデータをフェッチするテストを書いたら便利という話です。
Webmock
外部APIからデータをフェッチするクラスの単体テストを書きたい。そんなことってよくありますよね。
そんな時にwebmockというgemが使えるようです。
今回は、json placeholderからフェッチした値をテストしながらwebmockについて学んでみたいと思います。
概念を理解することを目的としているので、rspecの準備や環境を省いている点はご了承ください。
# model/Jsonplaceholder.rb
require 'net/https'
require 'uri'
class Jsonplaceholder < ApplicationRecord
def self.access(endpoint)
begin
uri = URI("https://jsonplaceholder.typicode.com/#{endpoint}")
response = Net::HTTP.get_response(uri)
puts response.code
rescue => e
puts e
end
end
end
こちらのクラスを実行すると、下記のようなデータが返ってきます。
#fixture/jsonplaceholder/response.json
[
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
},
{
"id": 2,
"name": "Ervin Howell",
"username": "Antonette",
"email": "Shanna@melissa.tv",
"address": {
"street": "Victor Plains",
"suite": "Suite 879",
"city": "Wisokyburgh",
"zipcode": "90566-7771",
"geo": {
"lat": "-43.9509",
"lng": "-34.4618"
}
},
"phone": "010-692-6593 x09125",
"website": "anastasia.net",
"company": {
"name": "Deckow-Crist",
"catchPhrase": "Proactive didactic contingency",
"bs": "synergize scalable supply-chains"
}
},
......
}
]
このクラスが正常に機能しているかテストをするためには、下記の項目をクリアしないといけませんよね。
- APIを叩いた時にstatus code 200が返ってきている
- 返ってきた値が期待する値と同じ
レスポンスが200かどうか確認するために毎回APIを叩くことは、外部APIに対して負荷を多くかけてしまうことや、テストが肥大化していくと通信時間が長くなってしまい全体のテスト時間が長くなってしまうといったことがあるので色々と不便。
ひとまず200が返ってくる前提で、返ってきた値が期待している値と同じかどうかを試したい。
そんな時に、webmockが使えます。
実装
#spec/jsonplaceholder_spec.rb
require 'rails_helper'
require 'webmock/rspec'
RSpec.describe Jsonplaceholder do
describe "#access" do
context 'when success access' do
before do
jsonplaceholder_request('users')
end
it do
expect{ subject }.not_to raise_error
end
end
end
def jsonplaceholder_request(endpoint)
WebMock.stub_request(:get, "https://jsonplaceholder.typicode.com/#{endpoint}")
.to_return(
body: File.read("#{Rails.root}/test/fixtures/jsonplaceholder/response.json"),
status: 'OK',
headers: { 'Content-Type' => 'application/json'}
)
end
end
webmockを使用するために、上部で読み込んでいます。この記述のおかげで、stub_request
を使えるようになります。
require 'webmock/rspec'
stub_request(:any, "www.example.com")
Net::HTTP.get("www.example.com", "/") # ===> Success
subjectを使っています。これは、describeもしくはcontextの””で囲まれた部分(今回だと#access)にリクエストを飛ばしてくれるrspecのDSLです。subjectと書くだけで、Jsonplaceholder#accessに対してリクエストを送ってくれます。
describe "#access" do
context 'when success access' do
before do
jsonplaceholder_request('users')
end
it do
expect{ subject }.to have_http_status(:ok)
end
end
end
同一ファイルないで複数のテストケースを書く場合は、「このsubjectってどこに対してリクエストしてるんだ?」とページをスクロールして確認しないといけないので、あまり推奨はされていないみたいです。
僕がRSpecでsubjectを使わない理由 - give IT a try
stub_request
は、webmockをrequireしたおかげで使用できるようになった記述です。stub = double(hoge) allow(stub).to receive(:huga).and_return(1)
というような記述に似ていますね。
def jsonplaceholder_request(endpoint)
WebMock.stub_request(:get, "https://jsonplaceholder.typicode.com/#{endpoint}")
.to_return(
body: File.read("#{Rails.root}/test/fixtures/jsonplaceholder/response.json"),
status: 'OK',
headers: { 'Content-Type' => 'application/json'}
)
end
後述
jsonplaceholderを外部APIとして使ったのであまり利点がわかりずらくなってしまいました。
SalesforceとRailsを連携させたプロジェクトなど、外部APIの仕様が複雑な場合は便利だと思います。
他にも、vcrというモック機能を提供できるgemがあるようです。
webmockだと、別途データ作成や管理が必要でしたが、vcrがあると全てよしなにやってくれるようです。
VCRを使うとRSpecのWebmockの作成が超絶楽になった!
ブラックボックステストに便利なツールを積極的に使用して、健やかなRspecライフを。
参考文献
VCRを使うとRSpecのWebmockの作成が超絶楽になった! 僕がRSpecでsubjectを使わない理由 - give IT a try
Discussion