柴ブログ

プログラミング奮闘記

RSpecでWebMockとVCRを使ったテストを書く

はじめに

表題の通り、RSpecでWebMockとVCRを使ったテストを初めて書いたのでその学びをまとめます。

Twitter APIを利用したRailsアプリでの実装。

環境

各gem

WebMock

外部APIを叩くアプリにおいて、テストを書く際に擬似的なAPIのレスポンスを作成し、そのレスポンスを元にテストを実行します。

その際のAPIレスポンス(モック)を定義できるgem。

VCR

上記のモックは基本的には自分で書いて用意しますが、その手間を省けるgem。

モックデータが存在していなければ、実際にAPIを叩いてそのリクエストとレスポンスをYAMLファイルとして自動で作成してくれる。

以降はそのYAMLファイルをモックとしてテストを実行できる。

セットアップ

Gemfileに以下を追加しbundleコマンド実行。

gem "webmock"
gem "vcr"

spec/rails_helper.rbに下記を追加し、RSpecでWebMockを使えるようにする。

require "webmock/rspec"

VCRの設定

spec/spec_helper.rb にVCRの設定を書いていきます。

require "vcr"
# ...

VCR.configure do |c|
  c.cassette_library_dir = "spec/vcr"
  c.hook_into :webmock 
  c.allow_http_connections_when_no_cassette = true
  c.configure_rspec_metadata! 
  c.ignore_localhost = true 
  c.ignore_hosts "chromedriver.storage.googleapis.com" 
  c.default_cassette_options = {
    record: :new_episodes, 
    match_requests_on: [:method, :path, :query, :body] 
  }
end

VCRはモックデータを「カセット」と呼ぶので、cassette = モックデータであり、カセットを使用することを「再生」と呼んでいます(シャレだ...)。

各設定については以下の通り。

  • cassette_library_dir = "spec/vcr"...カセットを保存するルートディレクト
  • hook_into :webmock...利用するモックライブラリを指定
  • allow_http_connections_when_no_cassette = true ...VCRを使わない場所ではHTTP通信を許可する
  • configure_rspec_metadata!...RSpecとの連携
  • ignore_localhost = true...ローカルホストのリクエストを無視し、Capybaraのリクエストに干渉しないようにする
  • ignore_hosts "chromedriver.storage.googleapis.com"...指定したホスト(chromedriver.storage.googleapis.com)へのリクエストを無視する
  • record: :new_episodes ...カセットがなければAPIをコールしてそれを記録する
  • match_requests_on: [:method, :path, :query, :body] ...カセットを引き当てる条件。今回はリクエストのメソッドとパス、 クエリそしてリクエストボディが一致するカセットを再生するという意味。

テストコードでVCRを使用

外部APIへのリクエストが生じるテストコードでVCRを使用するには以下のようにカセット名を指定します。

# テストコードを一部抜粋
describe "記事一覧", vcr: "twitter_api_response" do
  it "user_1の記事が表示される" do
    visit articles_path
    expect(page).to have_content "2020-10-17"
  end
end

もしくは以下のような書き方の方が明示的で読みやすい。

describe "記事一覧", vcr: { cassette_name: "twitter_api_response" } do
  it "user_1の記事が表示される" do
    visit articles_path
    expect(page).to have_content "2020-10-17"
  end
end

これでテストを実行すると、指定したカセットが存在していなければAPIを叩いてそのレスポンスをYAMLファイルで保存してくれます。

今回の場合はspec/vcr/twitter_api_response.yml が保存されます。

次またテストを実行するとこのファイルをモックとして参照してテストを実行してくれます。便利。

モックに秘密情報を載せたくない場合

今回はTwitter APIを叩いてテストを実行していますが、保存されたカセットにはリクエスト時のAPI Keyとアクセストークンが平文で記載され保存されていたので、これをいい感じにマスクしたい。

VCRの設定に以下を追記する。

# spec/spec_helper.rb

VCR.configure do |c|
# 省略
  c.filter_sensitive_data("<API_KEY>") { ENV["TWITTER_API_KEY"] }
  c.filter_sensitive_data("<ACCESS_TOKEN>") { ENV["ACCESS_TOKEN"] }
end

隠したい文字列(API Keyやアクセストークン)を完全一致で指定します。今回の例でいうとENV["TWITTER_API_KEY"] の部分。

実際にはこの環境変数API Keyを格納し、この設定上でも環境変数で指定しておく。

こうしておくとカセットに平文で乗っていたAPI Keyそのものが"<API_KEY>" という文字に置き換わる。

カセットを一度削除してからもう一度テストを実行すると上記の設定が反映されたモックが作成される。

参考