LoginSignup
6
4

More than 5 years have passed since last update.

[VCR][RSpec] WebMock.stub_requestに失敗していないかを簡単に確かめたい

Last updated at Posted at 2015-05-25

概要

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も用意されていますが、こちらは情報量が多すぎてノイジーなので自分はあまり使っていません。

spec/spec_helper.rb
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サービスを呼び出すテストを実行してみます。

モデル

app/models/weather.rb
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

テストコード

spec/models/weather_spec.rb
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漏れするマイあるある。

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4