vcrでコマンドライン(環境変数)でrecordモードを切り替える、他

基本的な使い方は以下を参照

[vcr (GitHub)](https://github.com/vcr/vcr)
[documentation (Relish)](https://relishapp.com/vcr/vcr/docs)
[RailsCasts #291 Testing with VCR](http://railscasts.com/episodes/291-testing-with-vcr)

# vcrでコマンドラインからrecordモードを切り替える

コマンドラインから以下のように、recordモードを上書きできる

```sh:
$ RECORD=all rspec spec/foo_spec.rb

$ RECORD=new_episodes rspec spec/bar_spec.rb:15
```

spec_helperなどに以下を記述 （`default_cassette_options`の部分）

```rb:spec/spec_helper.rb
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前に消す必要もあり面倒）

[参考1](https://github.com/avdi/leadlight/blob/4f8862f6e51ed25c5c87370e9d185463503d2d3e/spec/support/vcr.rb#L15) [参考2](https://github.com/shutl/shutl_auth/blob/2707616d57287f719678583f60b7e3ab4a2916a5/spec/spec_helper.rb#L16) [参考3](https://github.com/daviddavis/pkgwat/blob/07b9f77206a1e75780d1a073b891da68471f4434/test/test_helper.rb#L12-L18)

# vcrのcassettesの生成した内容を見やすく変換する

vcrのcassettesのymlファイルを見てもレスポンスのbodyが読めない

Guardのプラグインを使って、ymlの隣にtxtを出力する (webmockは動作確認済み)
`puts`, `f.write`の部分は好きなように変更すると良い

```rb:Guardfile
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
```
[URI without param(s)](https://www.relishapp.com/vcr/vcr/docs/request-matching/uri-without-param-s)

具体的なエラーの例

```
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|` に変更)

```rb
  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/`などの複数環境が混在している場合など

[Custom VCR matchers](http://railsware.com/blog/2013/10/03/custom-vcr-matchers-for-dealing-with-mutable-http-requests/)

# その他configurationオプション

* ignore_hosts [Ignore Request](https://www.relishapp.com/vcr/vcr/docs/configuration/ignore-request)
* ignore_localhost
* filter_sensitive_data [Filter sensitive data](https://www.relishapp.com/vcr/vcr/docs/configuration/filter-sensitive-data)
* debug_logger [Debug Logging](https://www.relishapp.com/vcr/vcr/docs/configuration/debug-logging)
* preserve_exact_body_bytes / uri_parser / query_parser

# vcrのcassettesの名前をカスタマイズする

rspecのexampleの文字をファイルのパスにする
[RailsCasts #291 Testing with VCR](https://github.com/railscasts/291-testing-with-vcr/blob/master/zip_coder-after/spec/support/vcr.rb) から

```rb
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文字に制限されているらしい

ファイルを最大文字数でカットする

```rb
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](http://stackoverflow.com/questions/14371686/building-rails-3-engine-throwing-gempackagetoolongfilename-error)

# vcrのcassettesの名前にspec名を追加する

利点としては、特定のspecのcassettesを探したり削除しやすくなる

`spec/features/foo/bar_spec.rb`なら
`spec/fixtures/vcr_cassettes/foo/bar/xxx_description_yyy.yml`のようになる

```rb
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を削除する

[Cleaning up unused cassettes](https://github.com/vcr/vcr/issues/283)

# vcrをRAILS_ENV=developmentで使う

### vcr_cable

[vcr_cable](https://github.com/spaghetticode/vcr_cable)

gem追加後、
`$ bundle exec rails generate vcr_cable`

```yaml:config/vcr_cable.yml
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](https://github.com/unixcharles/vcr-remote-controller)

# Rack::VCR

新しい
[Rack::VCRでらくらくアプリケーション間テスト](http://techlife.cookpad.com/entry/2015/10/09/125108)

[rack-vcr](https://github.com/miyagawa/rack-vcr)
[rack-vcr-sample](https://github.com/hogelog/rack-vcr-sample)

# vcrのcassettes一覧を管理画面インターフェースで閲覧、削除する

[mr_video](https://github.com/quidproquo/mr_video)

サブディレクトリに対応した模様
~~そのままだと`cassette_library_dir`の直下しか取り扱わない挙動
ディレクトリ配下を対象にする以下のIssueがある~~
[Add Support for cassette_library_dir with nested folders](https://github.com/quidproquo/mr_video/issues/1)

## 他

[リクエストパラメータのArrayに括弧をつけない](http://qiita.com/quattro_4/items/dac862e95c5414eba890)