I’ve not found any blog post or article about how to easily test CORS support implemented in a Rack middleware (e.g. using the rack-cors gem) with RSpec in Rails. So, after figured out I decided to write some small tips I learned from it.
I assume you have a fairly updated Rails app, I tested it with Rails 4.2.x, with RSpec tests support, and you want to add support for CORS headers.
##Install rack-cors gem
it’s better to follow the gem instructions (you’ll find the most updated instructions there) than a blog post, but anyway here’s how I did it:
Add this to your Gemfile:
gem 'rack-cors', :require => 'rack/cors'
Add this to your config/application.rb:
config.middleware.insert_before(0, "Rack::Cors", logger: (-> { Rails.logger })) do
allow do
origins '*'
resource '*', headers: :any, methods: [:get, :post, :patch, :options]
end
end
These are the common options, tune it to your needs. Here we’re allowing requests from any domain for the get, post, patch and options methods. options is a special method used for preflight requests.
That’s it. CORS is now active in this Rails app. Every time we send a request with the CORS headers, we’ll receive the CORS response headers.
##How to test it with cURL
With cURL, it’s easy to test CORS. Let’s see the most common scenarios:
####Sending the Origin header:
-H: Header to include in the request.-I: Fetch theHTTPheader only.
curl -H "Origin:*" -I http://localhost:3000/people/1234
We should receive: Access-Control-Allow-Origin: *.
####Sending the preflight options method:
Send a options request with Origin: *, Access-Control-Request-Method: get and Access-Control-Request-Headers: test.
-X: Specifies a custom request method
curl -X OPTIONS -I http://localhost:3000 -H 'Origin: *' -H 'Access-Control-Request-Method: GET' -H 'Access-Control-Request-Headers: test'
We should receive Access-Control-Allow-Origin: *, Access-Control-Allow-Methods: get, post, patch, options, and Access-Control-Allow-Headers: test.
##How to test it with RSpec
Support CORS is a bit tricky to test with RSpec, because we don’t have a real server (like Webrick, Unicorn or Puma) between the code and the client. This usually is not a problem but it does affect in this particular case, and the rfc3875 explains why. The server translate the custom HTTP request headers in this way:
- Convert to upper case.
- Replace
-with_ - Prepend
HTTP_.
Because we don’t have the server to do it for us, we must do it manually. For example, the Origin becomes HTTP_ORIGIN and Http-Access-Control-Request-Method becomes HTTP_ACCESS_CONTROL_REQUEST_METHOD.
Let’s see some example tests:
####Sending the Origin header:
RSpec.feature "the requests support CORS headers", type: :feature do
scenario 'Returns the response CORS headers' do
get '/people/1234', nil, 'HTTP_ORIGIN' => '*'
expect(last_response.headers['Access-Control-Allow-Origin']).to eq('*')
end
end
####Sending the preflight options method:
RSpec.feature "the requests support CORS headers", type: :feature do
scenario 'Send the CORS preflight OPTIONS request' do
options '/', nil, 'HTTP_ORIGIN' => '*', 'HTTP_ACCESS_CONTROL_REQUEST_METHOD' => 'GET', 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS' => 'test'
expect(last_response.headers['Access-Control-Allow-Origin']).to eq('*')
expect(last_response.headers['Access-Control-Allow-Methods']).to eq('GET, POST, PATCH, OPTIONS')
expect(last_response.headers['Access-Control-Allow-Headers']).to eq('test')
expect(last_response.headers).to have_key('Access-Control-Max-Age')
end
end
Note: You must use integration tests to be able the test through rack. A controller test doesn’t trigger rack, it’s like an unit test for the controller.
Don’t forget to uppercase, replace - and prepend HTTP_ to all your custom HTTP headers when testing!