基本的な使い方は以下を参照
vcr (GitHub)
documentation (Relish)
RailsCasts #291 Testing with VCR
vcrでコマンドラインからrecordモードを切り替える
コマンドラインから以下のように、recordモードを上書きできる
$ RECORD=all rspec spec/foo_spec.rb
$ RECORD=new_episodes rspec spec/bar_spec.rb:15
spec_helperなどに以下を記述 (default_cassette_options
の部分)
VCR.configure do |c|
c.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
c.hook_into :webmock # or :fakeweb
c.default_cassette_options = { record: ENV.fetch('RECORD'){ :once }.to_sym }
end
これをしないと、記録状態を変更しようとするたびに describe "foo", record: :all do
といったrecordオプションを付加しないといけないのでわずらわしい。(通常commit前に消す必要もあり面倒)
vcrのcassettesの生成した内容を見やすく変換する
vcrのcassettesのymlファイルを見てもレスポンスのbodyが読めない
Guardのプラグインを使って、ymlの隣にtxtを出力する (webmockは動作確認済み)
puts
, f.write
の部分は好きなように変更すると良い
require 'guard/plugin'
require 'json'
module ::Guard
class VcrReader < ::Guard::Plugin
def run_all
target_path = 'spec/fixtures/vcr_cassettes'
puts "Converting all request/response data in #{target_path}."
Dir.glob("#{target_path}/**/*\.yml").each do |path|
generate_pretty_log_from_yml(path)
end
end
def run_on_changes(paths)
puts "Converting request/response data. #{paths.inspect}"
paths.each do |path|
next unless File.exists?(path)
log_path = generate_pretty_log_from_yml(path)
puts "Generated #{log_path}"
end
end
private
def generate_pretty_log_from_yml(path)
record = YAML.load_file(path)
log_path = path.gsub(/\.yml/, '.txt')
File.open(log_path, 'w') do |f|
record["http_interactions"].each do |interaction|
request_method = interaction["request"]["method"]
f.write('='*80+"\n")
f.write('Request '+'-'*80+"\n")
f.write(interaction["recorded_at"]+"\n")
f.write("#{request_method.upcase} #{interaction["request"]["uri"]}"+"\n")
f.write(arrange_body_string interaction["request"]["body"]["string"]) if request_method == 'post'
f.write("\n")
f.write('Response '+'-'*80+"\n")
f.write(arrange_body_string interaction["response"]["body"]["string"])
f.write("\n")
end
end
log_path
end
def arrange_body_string(string)
begin
JSON.pretty_generate JSON.parse(string)
rescue
string
end
end
end
end
guard :vcr_reader do
watch(%r{^spec/fixtures/vcr_cassettes/.+\.yml$})
end
出力の変換結果例
================================================================================
Request --------------------------------------------------------------------------------
Tue, 08 Jul 2014 04:02:06 GMT
POST http://test.com/login.json
{
"body": {
"login": "test_user",
"password": "secret"
}
}
Response --------------------------------------------------------------------------------
{
"body": {
"loign": "test_user",
"code": "ABC",
"name": "テストユーザー",
"lang": "ja"
},
"status": "success"
}
================================================================================
Request --------------------------------------------------------------------------------
...
録画した内容(cassettes内のリクエスト)の一致ルール match_requests_on
日時、日付など動的な特定のパラメータの値を無視するようにする
日時、日付など特定のパラメータの値をテストで使っていると、日付が変わったり、年度が変わったりするとcassetteが見つからないエラーで失敗するようになるテストができる。(例: date
, starts_at
, start_time
, year
など)
日付以外にも、毎回変わるものの例に新規作成で発行されたIDなどがある(createしてそのIDを元に以降のページをテストしている場合など)
それらのパラメータをcassetteのマッチング時に無視するような設定は以下のようにできる。
describe "SomeTest with Date", vcr: true, match_requests_on: [:method, VCR.request_matchers.uri_without_param(:date)] do
具体的なエラーの例
VCR::Errors::UnhandledHTTPRequestError:
========================================
An HTTP request has been made that VCR does not know how to handle
GET http://example.com/foo?date=20151119&...
パラメータ全体を無視する
パラメータ全体を無視することも可能
optionの:body
, :query
などを除く (match_requests_on: [:method, :path]
)
ただし、この場合は副作用が起こることがある。
同じpathに対して違うリクエストをするものが同一cassettesに入った時などに、すべて同じ(最初にマッチした)リクエストが返されてしまう。
match_requests_onの設定を調べる
各specの各exampleでどの設定になっているかを調べるには
(注. do |example|
に変更)
it "..." do |example|
puts example.metadata[:match_requests_on]
...
デフォルトの設定を調べるにはVCR.configuration.default_cassette_options
やVCR.configuration
などを出力してみると良い
他のパターン
-
:path
を使えば違う環境(ドメイン)に対して動かすことができる- 例えば、
http://stage1.example.com/
,http://stage2.example.com/
などの複数環境が混在している場合など
- 例えば、
その他configurationオプション
- ignore_hosts Ignore Request
- ignore_localhost
- filter_sensitive_data Filter sensitive data
- debug_logger Debug Logging
- preserve_exact_body_bytes / uri_parser / query_parser
vcrのcassettesの名前をカスタマイズする
rspecのexampleの文字をファイルのパスにする
RailsCasts #291 Testing with VCR から
RSpec.configure do |c|
c.around(:each, :vcr) do |example|
name = example.metadata[:full_description].split(/\s+/, 2).join("/").underscore.gsub(/[^\w\/]+/, "_")
options = example.metadata.slice(:record, :match_requests_on).except(:example_group)
VCR.use_cassette(name, options) { example.call }
end
end
例えば spec/fixtures/vcr_cassettes/zip_code_lookup/show_beverly_hills_given_90210.yml
のようなファイル名になる
ファイル名の文字数を制限する
gemの中でvcrを使っていたときに、そのgemをプロジェクトからbundleするとGem::Package::TooLongFileName
が出ることがあった
rubygemsでファイルの長さが100文字に制限されているらしい
ファイルを最大文字数でカットする
c.around(:each, :vcr) do |example|
name = example.metadata[:full_description].split(/\s+/, 2).join("/").underscore.gsub(/[^\w\/]+/, "_")
# NOTE Fix 'Gem::Package::TooLongFileName'
name = name.split('/').map{|s| s[0..93] }.join('/')
StackOverflow : Building Rails 3 Engine Throwing Gem::Package::TooLongFileName Error
vcrのcassettesの名前にspec名を追加する
利点としては、特定のspecのcassettesを探したり削除しやすくなる
spec/features/foo/bar_spec.rb
なら
spec/fixtures/vcr_cassettes/foo/bar/xxx_description_yyy.yml
のようになる
c.around(:each, type: :feature) do |example|
spec_name = example.metadata[:file_path].gsub('./spec/features/', '').gsub('_spec.rb', '')
desc_name = example.metadata[:full_description].split(/\s+/, 2).join("/").underscore.gsub(/[^\w\/]+/, "_")
name = [spec_name, desc_name].join("/")
使われていないcassettesを削除する
vcrをRAILS_ENV=developmentで使う
vcr_cable
gem追加後、
$ bundle exec rails generate vcr_cable
development:
hook_into: webmock
cassette_library_dir: spec/fixtures/vcr_cassettes
enable_erb: false
allow_playback_repeats: false
allow_http_connections_when_no_cassette: true
enable_vcr_cable: true
有効にする方法は、enable_vcr_cable: true
にするか、ENABLE_VCR_CABLE=true bundle exec rails s
でサーバーを起動する
意外にちゃんと動いた。あくまでモックの扱いなので、動的なアクション・データを扱いたい場合は使えなくなる。
spec/fixtures/vcr_cassettes/vcr_cable_cassette.yml
にカセットができるが、カセットが一つなのでファイルが肥大して問題になることもありそう。
他
他見つけたやつ。古い、試していない vcr-remote-controller
Rack::VCR
新しい
Rack::VCRでらくらくアプリケーション間テスト
vcrのcassettes一覧を管理画面インターフェースで閲覧、削除する
サブディレクトリに対応した模様
そのままだとcassette_library_dir
の直下しか取り扱わない挙動
ディレクトリ配下を対象にする以下のIssueがある
Add Support for cassette_library_dir with nested folders