Rails開発でWebMockを使ってAPIアクセスをスタブ化する
外部API呼び出しのあるアプリケーションを開発する場合、テストやローカル環境での動作確認まで本物のAPIを呼び出していると、時間がかかり開発の生産性が下がるのでAPIモックを使いたくなります。
そこでWebMockというGemを使うと、外部へのHTTPリクエストをスタブ化してくれるので、開発が非常にやりやすくなります。
今回はRailsアプリケーションでのWebMockの使い方を説明します。
インストール方法
Gemfileに書いてbundlerでインストールします。
gem 'webmock'
bundle install --path vendor/bundle
設定方法
事前に自分の欲しいレスポンスを返却できるようにスタブを登録します。
(ここではレスポンスデータを事前に作成して、そのファイルを読み込むように指定しています)
require 'webmock' WebMock.enable! # GETリクエストのスタブ登録 WebMock.stub_request(:get, "http://www.example.com").to_return( body: File.read("#{Rails.root}/test/fixtures/stub_api_response.json"), status: 200, headers: { 'Content-Type' => 'application/json' }) # PUTリクエストのスタブ登録 WebMock.stub_request(:put, "http://www.example.com").to_return( body: '{"key": "value"}', status: 200, headers: { 'Content-Type' => 'application/json' }) # 同じように他HTTPメソッドのリクエストもスタブ登録できます
これでスタブの設定はできたので、該当のAPIを呼び出すと設定したレスポンスデータが返却されるのですが、WebMockはデフォルト設定だとスタブ登録していないAPIのアクセスを許可していません。
スタブ化していないAPIのアクセスを許可するには以下の記述を追加します。
require 'webmock' WebMock.allow_net_connect!(:net_http_connect_on_start => true)
RSpec
テストでも外部APIへのリクエストをスタブ化します。同じようにスタブ登録するだけです
require 'rails_helper' require 'webmock/rspec' describe ApiTest before do WebMock.enable! end describe '#get api' do context '200 status' do before do # ここでテスト対象コードの中に実装されている外部API呼び出しをstub登録します WebMock.stub_request(:get, "http://www.example.com").to_return( body: File.read("#{Rails.root}/test/fixtures/stub_api_response.json"), status: 200, headers: { 'Content-Type' => 'application/json' }) end it 'returns 200 status' do # ここにテストコードが入ります end end end end
アプリケーションへの組み込み
次はRailsアプリケーションへWebMockを組み込む方法を説明します。
WebMockをアプリケーションコードに組み込むときに以下のような制約があるとします。
- development, test環境のみ使用する。staging、production環境ではWebMockをインストールしたくない
- development環境でも、WebMockを使用する場合と使用しない場合で使い分けたい
- 外部API呼び出しの実装はModule化している
この場合のWebMockの組み込みの一例を書いてみます
まず、Gemfileのdevelopment, test環境にのみ以下の記述を追加します。
require false
のオプションを追加して明示的に読み込むようにします。
group :development, :test do gem 'webmock', require: false end
次はdevelopment.rb
とtest.rb
にWebMockを読み込むように記述します。
今回は以下のように設計します。
- development環境でWebMockを使用する場合は環境変数に値を設定する
test/stub/api_stub.rb
ファイルに外部API呼び出しをstub登録するコードを実装する
development.rb
if ENV["APIMODE"] == "stub" # 環境変数の有無を判定します require 'webmock' # ここで明示的に読み込みます WebMock.allow_net_connect!(:net_http_connect_on_start => true) require_relative '../../test/stub/api_stub' end
test.rb
require 'webmock' WebMock.allow_net_connect!(:net_http_connect_on_start => true)
stub登録の実装はtestディレクトリのファイルなので、ここで環境変数が設定されていない場合や、staging、production環境の場合は読み込まれません。
stub登録の実装は今までの説明と同じ内容ですが、stub登録のコードを実行後に本来の外部API呼び出しのコードを実行するため、最後にsuper
の記述を追加しています。
api_stub.rb
module ApiStub WebMock.enable! # アプリケーションコードに`call_get_api`という名称の外部API呼び出し実装を含むメソッドがあることして、ここで同名のメソッドを作成してstub登録します。 def call_get_api WebMock.stub_request(:get, "http://www.example.com").to_return( body: File.read("#{Rails.root}/test/fixtures/stub_api_response.json"), status: 200, headers: { 'Content-Type' => 'application/json' }) super # アプリケーションコードの実装を呼び出す end end
次はアプリケーションコードからこのstub登録をするファイルを読み込むように設定します。
WebMockの使用有無を判定する環境変数とRAILS_ENVの二つを確認してstub登録の実装を読み込んでいます。
module ApiModule prepend ApiStub if ENV["APIMODE"] == "mock" && Rails.env.development? def call_get_api # ここに本来の外部API呼び出しの実装が入る end end
ここではprependでスタブ実装のmoduleを読み込んでいます。そのためこのモジュールを読み込んだクラスの継承ツリーは以下のようになります。
- ApiModule(外部API呼び出し実装)
- ApiStub(stub登録コード)
- モジュールを読み込んだクラス
stub登録の実装で最後にsuper
と記述しているのは、この継承ツリーが作られることを前提に実装しているからです。
最後に外部API呼び出しのmoduleを使用するクラスですが、通常通りmoduleをincludeするだけです。
api_client.rb
class ApiClient include ApiModule # ここに実装が入ります end
まとめ
WebMockを使えば外部API呼び出しのmockを自分で実装しなくても簡単にstub登録できます。
外部API呼び出しのあるRailsアプリケーションを開発する場合はWebMockの使用を検討してみてください。