外部サイトへのアクセスが発生する、 Railsアプリがあるとします。
このRailsアプリに対するテストコードでは、外部サイトへのアクセスが発生しないよう、 WebMock
を使うのが便利です。
bblimke/webmock: Library for stubbing and setting expectations on HTTP requests in Ruby.
ただ、
stub_request(:get, 'https://example.com').to_return(status: 200)
のようにモックしたところ
WebMock::NetConnectNotAllowedError: Real HTTP connections are disabled. Unregistered request: GET https://example.com?foo=bar
というエラーが発生したため、メモを残します。
目次
環境
原因
エラーメッセージにもある通り、WebMockではクエリ文字列も考慮して stub_request
する必要があるようです。
例えば、RailsアプリのControllerに外部APIを呼ぶ処理があるとします*1。
ここで、外部APIは以下のFake APIを使うとします。
JSONPlaceholder - Free Fake REST API
module Api class PostsController < ApplicationController def index # 無意味なリクエストだけど、本来は受け取ったデータを元にあれこれする # See https://jsonplaceholder.typicode.com/ connection = Faraday.new(url: 'https://jsonplaceholder.typicode.com') _ = connection.get '/comments?postId=1' render json: { data: 'ok' } end end end
このControllerに対し、routesにて
Rails.application.routes.draw do namespace 'api' do get 'posts', to: 'posts#index' end end
と設定されていたとします。
そんな中、テストコードで WebMock + RSpec を使うため、
% bin/rails generate rspec:install
で生成した spec/rails_helper.rb
に
require 'webmock/rspec'
と設定し、 spec/request/api/posts_spec.rb
に
require 'rails_helper' RSpec.describe 'Post API', type: :request do describe 'GET /api/posts' do context 'stub失敗' do before do stub_request(:get, 'https://jsonplaceholder.typicode.com/comments').to_return(status: 200) end it 'リクエストが成功すること' do get '/api/posts' expect(response).to have_http_status(200) end end end end
というテストコードを書くと、冒頭のようなエラーメッセージが表示されます。
WebMock::NetConnectNotAllowedError: Real HTTP connections are disabled. Unregistered request: GET https://jsonplaceholder.typicode.com/comments?postId=1 with headers {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Faraday v1.8.0'} You can stub this request with the following snippet: stub_request(:get, "https://jsonplaceholder.typicode.com/comments?postId=1"). with( headers: { 'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Faraday v1.8.0' }). to_return(status: 200, body: "", headers: {}) registered request stubs: stub_request(:get, "https://jsonplaceholder.typicode.com/comments")
対応
stub_requestのURLにクエリ文字列を含める
エラーメッセージの
You can stub this request with the following snippet
の snippet をそのまま使ってもよいのですが、テストをパスさせるだけならば、 URL にクエリ文字列を含めるだけでも大丈夫です。
context 'URLにクエリ文字列を含める' do before do stub_request(:get, 'https://jsonplaceholder.typicode.com/comments?postId=1').to_return(status: 200) end it 'リクエストが成功すること' do get '/api/posts' expect(response).to have_http_status(200) end end
stub_request + with でクエリ文字列用のハッシュを渡す
上記のように、クエリ文字列を含んだURLを stub_request に渡すのもよいのですが、クエリ文字列の整形が大変です。
公式ドキュメントによると、 with(query: {})
のような形式でクエリ文字列を渡すこともできるようです。
https://github.com/bblimke/webmock#matching-query-params-using-hash
context 'withでクエリ文字列を指定' do before do stub_request(:get, 'https://jsonplaceholder.typicode.com/comments') .with( query: { 'postId' => 1 } ) .to_return(status: 200) end it 'リクエストが成功すること' do get '/api/posts' expect(response).to have_http_status(200) end end
stub_request に正規表現を渡す
今回の Controller では直接Faradayを使っていました。
ただ、場合によっては、Faraday などをラップしたクライアントが存在し、そのクライアントの中で動的にクエリ文字列を生成することもあるかもしれません。
WebMockでは、 stub_request()
に正規表現を渡すことで、正規表現にマッチしたURLにリクエストが飛ぶ時はモックしてくれるようです。
https://github.com/bblimke/webmock#matching-uris-using-regular-expressions
context '正規表現でstub' do before do stub_request(:get, %r{jsonplaceholder.typicode.com}) .to_return(status: 200) end it 'リクエストが成功すること' do get '/api/posts' expect(response).to have_http_status(200) end end
ソースコード
Githubに上げました。
https://github.com/thinkAmi-sandbox/rails_webmock-sample