###概要
WebMock.stub_requestはURIのマッチングに正規表現が使えるというデキる子ですが、VCRと一緒にテストを書いていると意図せずstubに失敗し、裏でこっそりとCassetteが自動生成されるという事態に遭遇することがあります([VCR][RSpec]VCRに依存しないテストコードを書くのように、テストコードにuse_cassetteを書かなくて済む設定がある場合)。この時、テストの実行結果からこれらの挙動が読み取れないため不便です。
###環境
- ruby 2.1.3p242 (2014-09-19 revision 47630) [x86_64-darwin13.0]
- rspec 3.1.7
- vcr 2.9.3
###やること
- テストの実行時に「Cassetteに書き込んだのか」「Cassetteを使ってstubしたのか」「WebMockによるstubなのか」を標準出力する
- テストの実行時にアクセスしたCassetteのファイルパスを標準出力する
ちなみにVCR標準のDebug Loggingも用意されていますが、こちらは情報量が多すぎてノイジーなので自分はあまり使っていません。
VCR.configure do | c |
c.allow_http_connections_when_no_cassette = true
c.hook_into :webmock
c.cassette_library_dir = "spec/vcr_cassettes"
c.configure_rspec_metadata!
c.default_cassette_options = { erb: true, record: :new_episodes }
c.before_record do | i |
i.response.body.force_encoding "UTF-8"
i.response.body = JSON.pretty_generate(JSON.parse(i.response.body))
end
c.around_http_request do | request |
VCR.use_cassette(request.parsed_uri.path, &request)
end
[ "recordable", "stubbed" ].each do | method |
c.after_http_request("#{method}?".to_sym) do | request, _response |
puts "- VCR - #{method} - [#{request.method}] #{request.parsed_uri}"
unless request.externally_stubbed?
puts " used cassette - #{VCR.current_cassette.try(:file)}"
else
puts " stubbed by WebMock"
end
end
end
# c.debug_logger = $stdout
end
過去記事の設定が混じっていますが、今回のメインは[ "recordable", "stubbed" ].each do | method |〜のブロックです。
###お天気WEBサービスを呼び出すテストを実行してみます。
モデル
class Weather < ActiveResource::Base
self.site = 'http://weather.livedoor.com'
self.prefix = '/forecast/webservice/json'
self.format = :json
self.include_format_in_path = false
self.element_name = ''
def self.forecast(params)
self.new get("v1", params)
end
def location_names
pinpointLocations.map &:name
end
end
テストコード
describe Weather, type: :model do
describe 'forecast' do
subject { Weather.forecast(params) }
context 'city-id is 020020' do
let(:params) { { city: '020020' } }
describe '#title', :vcr_erb => {:test_name => "{a:1}"} do
it { expect(subject.title).to eq '青森県 むつ の天気' }
end
describe '#location_names' do
it { expect(subject.location_names).to match ["むつ市", "大間町", "東通村", "風間浦村", "佐井村"] }
end
end
context 'city-id is 020030' do
before do
uri = URI.parse("#{Weather.site}#{Weather.prefix}/v1?city=020030").to_s
stub_request(:get, uri).to_return(body: '{"pinpointLocations":{}}')
end
let(:params) { { city: '020030' } }
it { expect(subject).to be_present }
end
end
end
テスト実行
$ spring rspec spec/models/weather_spec.rb
Weather
forecast
city-id is 020020
#title
- VCR - recordable - [get] http://weather.livedoor.com/forecast/webservice/json/v1?city=020020
used cassette - /(ないしょ)/spec/vcr_cassettes/forecast/webservice/json/v1.yml
should eq "青森県 むつ の天気"
#location_names
- VCR - stubbed - [get] http://weather.livedoor.com/forecast/webservice/json/v1?city=020020
used cassette - /(ないしょ)/spec/vcr_cassettes/forecast/webservice/json/v1.yml
should match ["むつ市", "大間町", "東通村", "風間浦村", "佐井村"]
city-id is 020030
- VCR - stubbed - [get] http://weather.livedoor.com/forecast/webservice/json/v1?city=020030
stubbed by WebMock
should be present
Finished in 0.14188 seconds (files took 62 minutes 51 seconds to load)
3 examples, 0 failures
3つの"- VCR -"が出力されています。
- 1つ目は"recordable"で、#titleのテスト実行により[get]〜のURIに実通信が生じ、その内容を"used cassette"が示すファイルに記録したことを表しています。
- 2つ目は"stubbed"で、先ほど自動生成したCassetteを再利用して実通信をstubしたことが分かります。
- 3つ目も"stubbed"ですが、こちらはWebMockによりstubされたことを表しています。
これで、stub_requestが開発者の意図通りに動作していない場合、それに気付きやすくなります。withやheadersがちょっと違ってstub漏れするマイあるある。