Rails + Grape + Rspec でサブドメイン(constraints)のテストを行う時は integration_session.host= を使う
問題
Rails.application.routes.draw do constraints subdomain: /^api/ do mount Api::HogeApi => '/hoge' mount Api::FugaApi => '/fuga' end constraints subdomain: /^(?!api)/ do # non API routes end
こんな感じのとき、Grapeのドキュメントにあるような
RSpec.configure do |config| config.include RSpec::Rails::RequestExampleGroup, type: :request, file_path: /spec\/api/ end
だけでは、
Failure/Error: get "/api/hoge/entries/1" ActionController::RoutingError: No route matches [GET] "/api/hoge/entries/1"
のようなエラーになる。
対策
コードを追って、最終的にこんな感じでGrapeテストの時のホスト名を設定するようにした。
# API config.include RSpec::Rails::RequestExampleGroup, type: :request, file_path: /spec\/api/ config.include RSpec::Rails::ViewRendering, type: :request, file_path: /spec\/api/ config.before do if self.class.described_class < Grape::API reset! unless integration_session integration_session.host = 'api.example.com' end end
※テスト中でreset!
して複数リクエストのテストを行う場合、毎回integration_session.host =
で設定する必要がある点に注意。
※default_url_options
を設定する方法ではうまくいかない。(リクエストの過程で参照されない)
過程
Rspec::Rails::RequestExampleGroup
include ActionDispatch::Integration::Runner
ActionDispatch::Integration::Runner
module Runner include ActionDispatch::Assertions def app @app ||= nil end # Reset the current session. This is useful for testing multiple sessions # in a single test case. def reset! @integration_session = Integration::Session.new(app) end def remove! # :nodoc: @integration_session = nil end %w(get post patch put head delete cookies assigns xml_http_request xhr get_via_redirect post_via_redirect).each do |method| define_method(method) do |*args| reset! unless integration_session # reset the html_document variable, except for cookies/assigns calls unless method == 'cookies' || method == 'assigns' @html_document = nil reset_template_assertion end integration_session.__send__(method, *args).tap do copy_session_variables! end end end
Integration::Session#get
def get(path, parameters = nil, headers_or_env = nil) process :get, path, parameters, headers_or_env end
Integration::Session#process
# Performs the actual request. def process(method, path, parameters = nil, headers_or_env = nil) if path =~ %r{://} location = URI.parse(path) https! URI::HTTPS === location if location.scheme host! "#{location.host}:#{location.port}" if location.host path = location.query ? "#{location.path}?#{location.query}" : location.path end hostname, port = host.split(':')
Integration::Session#host
# The hostname used in the last request. def host @host || DEFAULT_HOST end attr_writer :host
Integration::Session#host=
で設定できそう。
Integration::Session
インスタンスの取得は、ActionDispatch::Integration::Runner#integration_session
。
# Reset the current session. This is useful for testing multiple sessions # in a single test case. def reset! @integration_session = Integration::Session.new(app) end
private def integration_session @integration_session ||= nil end
# API config.include RSpec::Rails::RequestExampleGroup, type: :request, file_path: /spec\/api/ config.include RSpec::Rails::ViewRendering, type: :request, file_path: /spec\/api/ config.before do if self.class.described_class < Grape::API reset! unless integration_session integration_session.host = 'api.example.com' end end