KEIS BLOGは株式会社ケイズ・ソフトウェアが運営しています。

KEIS BLOG

[RSpec][VCR] WEB API呼び出しのテストをstubしてみる


松山です。

前回まで”MSXとKONAMIさん”と題して記事を書いていました。当初はドヤ顔で良いこと書いて締める予定だったのですが、月日の経過に伴いオチを失念したため連載3回という短命ですが打ち切りとさせてください。
「羽ばたけ未来へ!」
松山先生の次回作にご期待ください!

ということで、突然ですがVCRというgemを利用して、RSpecでのAPI呼び出しを簡単にstubする方法を書きます。

【やること】
・お天気webサービス(http://weather.livedoor.com/weather_hacks/webservice)を呼び出す処理の簡単なテストコードを書く
・テスト実行で通信が発生しないようVCRを利用してwebサービスの呼び出しをstubする
・テストコードがVCRに依存しない仕組みを作る

参考文献
・VCR(https://github.com/vcr/vcr)
・VCR – Relish(https://relishapp.com/vcr/vcr/v/2-9-3/docs)

環境
・ruby 2.1.3p242 (2014-09-19 revision 47630) [x86_64-darwin13.0]
・rspec 3.1.7
・rails 4.1.6
・vcr 2.9.3
・webmock 1.20.4

# Gemfile
group :test do
 gem 'rspec-rails'
 gem 'vcr'
 gem 'webmock'
end

group :development, :test do
 gem 'rspec'
 gem 'byebug'
end

【VCRの設定】

# spec/spec_helper.rb
require 'webmock/rspec'
require 'vcr'

RSpec.configure do |config|
 VCR.configure do | c |
   c.allow_http_connections_when_no_cassette = true
   c.hook_into :webmock
   c.cassette_library_dir = 'spec/vcr_cassettes'
   c.default_cassette_options = { record: :new_episodes }
   c.before_record { | i | i.response.body.force_encoding 'UTF-8' }
   c.around_http_request do | request |
     VCR.use_cassette(request.parsed_uri.path, &request)
   end
   # c.debug_logger = $stdout
 end
end

VCRのスタンダードな使い方は、

VCR.use_cassette('whatever cassette name you want') do
  # the body of the test would go here...
end

となっており、このコードを各テストケースに埋める必要があります。これはすこーし煩わしく、テストコードがVCRに依存するのでイケナイです。上記spec/spec_helper.rbの設定は全てのAPI呼び出し(※1)をVCRによってstubするもので、テストコードの非VCR依存を実現します。

※1正確にはWebMockによるNet::HTTPのフック

【モデルを作る】

# 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
end

ActiveResource::Baseを継承した「お天気」に該当するモデルです。ちなみにお天気APIは、
http://weather.livedoor.com/forecast/webservice/json/v1?city=400040
のように呼び出せますが、これはRestfulではないのでモデルでごにょごにょしています。

動作確認

 $ rails c
 > Weather.forecast city:'400040'
 => #<Weather:0x007fd3741a3a70 @attributes={...
 > Weather.forecast(city:'400040').pinpointLocations.first.name
 => "大牟田市"

この動作確認により、spec/vcr_cassettes/forecast/webservice/json/v1.yml というファイルが自動生成されます(ファイルの命名規則は前述のspec/spec_helper.rbによるもの)。これはVCRの”カセット”と呼ばれるもので、お天気APIのリクエストとレスポンスを記録したファイルです。

・初回リクエスト時は通信を行いカセットを生成(recording)
・次回以降は通信が発生しない、カセット再生によるstubリクエストを発行(playback)

というのがVCRの基本動作になります。

【RSpecでテストを書く】

# spec/model/weather_spec.rb
describe Weather do
 describe '.forecast' do

   subject { Weather.forecast(params) }

   context 'city-id is 020020' do
     let(:params) { { city: '020020' } }

     describe '.title' do
       it { expect(subject.title).to eq '青森県 むつ の天気' }
     end
   end
 end
end

実行結果

Weather
 .forecast
   city-id is 020020
     .title
       should eq "青森県 むつ の天気"

Finished in 0.0248 seconds (files took 84 minutes 34 seconds to load)
1 example, 0 failures
[Finished in 0.7s]

至って普通のテストコードです。VCR使うぞー!という記述は一切ありません。ステキです。

今回はここまでです。
次回以降は今回のソースコードをベースにVCRで出来ることを掘り下げていきながら、最終的にはWEB API呼び出しにおけるテストのベストプラクティスを模索したいと思いまっす。

写真1

みなさんiOSのアップデートはお済みですか(執筆時は2015.03)。回線が不調なのか、なかなか終わりません。

 

【関連記事】
MSXとKONAMIさん
MSXとKONAMIさん(その2)
MSXとKONAMIさん(その3)