VCRやWebMockを使った外部API連携のテスト戦略
現代のWebアプリケーション開発において、外部のAPIと連携することはごく一般的です。天気情報、決済サービス、SNS連携など、多くの機能が外部APIへのリクエストによって実現されています。
しかし、こうした外部API連携を含むコードのテストには、特有の難しさが伴います。
- 不安定さ: 外部サービスの障害やネットワークの問題で、テストが失敗することがある。
- 遅さ: 毎回実際にHTTPリクエストを送信すると、テストの実行が非常に遅くなる。
- コスト: APIの利用回数に応じて課金されるサービスの場合、テスト実行がコスト増に繋がる。
- 非決定性: APIのレスポンス内容が変わる可能性があり、テスト結果が安定しない。
これらの問題を解決し、外部API連携を安定して高速にテストするための強力なツールが、WebMockとVCRです。
この記事では、これらのgemを使ったテスト戦略について解説します。
WebMock: HTTPリクエストをスタブする
WebMockは、HTTPリクエストをインターセプトし、実際のネットワーク通信を行わずに、あらかじめ定義したレスポンスを返すように見せかける(スタブする)ためのライブラリです。
基本的な使い方
spec_helper.rbなどでWebMockを読み込みます。
# spec/spec_helper.rb
require 'webmock/rspec'テストコード内で、特定のエンドポイントへのリクエストをスタブします。
# spec/services/weather_service_spec.rb
describe WeatherService do
it "東京の天気を取得する" do
# http://api.weather.com/tokyo へのGETリクエストをスタブ
stub_request(:get, "http://api.weather.com/tokyo")
.to_return(
status: 200,
body: { tenki: "hare", temperature: 25 }.to_json,
headers: { 'Content-Type' => 'application/json' }
)
weather_data = WeatherService.fetch_weather_for("tokyo")
expect(weather_data["tenki"]).to eq("hare")
expect(weather_data["temperature"]).to eq(25)
# 実際にリクエストが送信されたかを検証することもできる
expect(a_request(:get, "http://api.weather.com/tokyo")).to have_been_made.once
end
endWebMockを使うことで、外部APIがどのような状態であっても、テストは常に同じ結果を返すようになり、安定性と速度が向上します。
しかし、APIのレスポンスが複雑な場合、to_returnに指定するbodyやheadersを手で書くのは非常に面倒です。そこで登場するのがVCRです。
VCR: HTTPリクエストを記録・再生する
VCRは、WebMockのようなリクエストスタブライブラリの上で動作し、HTTP通信を「カセット」と呼ばれるYAMLファイルに記録・再生してくれるツールです。
VCRの仕組み
- 記録 (Recording): テストの初回実行時、
VCRは実際のHTTPリクエストを外部に送信し、そのリクエストとレスポンスのペアをカセットファイル(例:spec/fixtures/vcr_cassettes/weather_api.yml)に記録します。 - 再生 (Replaying): 2回目以降のテスト実行時、
VCRは実際にはリクエストを送信しません。代わりに、カセットファイルに記録された内容を読み込み、リクエストが一致すれば、記録済みのレスポンスを返します。
これにより、開発者は手動でスタブを記述する必要がなくなり、実際のAPIレスポンスに基づいた正確なテストを、高速かつ安定して実行できるようになります。
導入と設定
Gemfileにvcrを追加し、bundle installします。
# Gemfile
group :test do
gem "vcr"
gem "webmock"
endspec_helper.rbでVCRの設定を行います。
# spec/spec_helper.rb
require 'vcr'
VCR.configure do |config|
config.cassette_library_dir = "spec/fixtures/vcr_cassettes" # カセットの保存先
config.hook_into :webmock # WebMockと連携
config.configure_rspec_metadata! # RSpecのメタデータと連携
# APIキーなどの機密情報をフィルタリングする設定
config.filter_sensitive_data('<API_KEY>') { ENV['WEATHER_API_KEY'] }
endfilter_sensitive_dataの設定は非常に重要です。APIキーやパスワードなどの機密情報がカセットファイルに記録されるのを防ぎます。
VCRを使ったテスト
テストコードでvcrメタデータを指定するだけで、そのテストブロック内のHTTP通信が自動的に記録・再生されます。
# spec/services/weather_service_spec.rb
RSpec.describe WeatherService, :vcr do # :vcr を追加
it "東京の天気を取得する" do
weather_data = WeatherService.fetch_weather_for("tokyo")
expect(weather_data["location"]).to eq("Tokyo")
expect(weather_data).to have_key("temperature_celsius")
end
endこれだけで、初回実行時にspec/fixtures/vcr_cassettes/WeatherService/東京の天気を取得する.ymlのようなカセットが自動生成され、2回目以降はそのカセットが使われます。
カセット名を明示的に指定することもできます。
RSpec.describe WeatherService do
it "大阪の天気を取得する", vcr: { cassette_name: 'weather_api/osaka' } do
# ...
end
endまとめとベストプラクティス
WebMockとVCRは、外部API連携を含むテストの信頼性と速度を劇的に向上させます。
WebMock: HTTPリクエストを直接スタブするための低レベルなツール。シンプルなテストや、意図的にエラーケースを作りたい場合に便利。VCR:WebMockを使いやすくする高レベルなツール。リクエストを記録・再生することで、リアルなデータに基づいたテストを簡単に実現できる。
ベストプラクティス
- 機密情報は必ずフィルタリングする:
filter_sensitive_dataを使い、APIキーなどがGitリポジトリにコミットされないように徹底する。 - カセットは定期的に更新する: 外部APIの仕様が変更される可能性に備え、古くなったカセットは定期的に削除して再記録するタスクを用意すると良い。
VCRとWebMockを使い分ける: 正常系のテストはVCRで楽をし、404エラーや500エラーといった異常系のテストはWebMockでstub_requestを使って意図的に作り出す、という使い分けが効果的です。
これらのツールを使いこなし、外部APIへの依存からテストを解放しましょう。