Testing your Rails APIs with Airborne.
It took me a long long time to finally decided to write tests. I’ll probably write about the thought process and why I think you should write tests later.
If you use Rails to build REST APIs, and you want the number of times the FE Engineer you’re working with complains that an endpoint isn’t working, this post is for you.
Airborne is an RSpec driven API testing framework that let’s you write tests for APIs built with Ruby in a very intuitive easy to grok way. Unfortunately, it’s documentation on working with Rails APIs isn’t as thorough as I would have liked.
So in this post, I’m going to touch on all the different aspects of using Airborne with Rails that weren’t clear at first to me. Besides the things I’m explaining here, everything else about using Airborne is well described in it’s documentation.
(Some experience with RSpec would make this easier to read/understand)
Installation
You have to install RSpec as Airborne is dependent on it.
group :development, :test do
gem 'rspec-rails', '~> 3.5'
endgroup :test do
gem 'airborne'
end
Add the above to your Gemfile
and run bundle install
.
Then run rails generate rspec:install
.
Once installed, RSpec will generate spec files instead of Test::Unit test files when commands like rails generate model
and rails generate controller
are used.
How to use
Introduction
Generally, in Rails REST API applications (that don’t use tools like Grape), your endpoints correspond to a controller action. Since Airborne is RSpec driven, it makes sense that your endpoint tests will live in the spec
for your controller.
For example, if you have a tickets_controller.rb
that handles all endpoints related to getting a list of tickets, creating tickets and editing tickets. Your API tests will live in /spec/controllers/tickets_controller_spec.rb
. As stated above RSpec will generate the appropriate spec files if you use the rails generate controller
command.
Making Requests
require 'rails_helper'
RSpec.describe TicketsController, :type => :controller do
describe 'GET index' do
it 'returns correct types' do
get :index
expect_json_types(foo: :string)
end
end
end
The above shows a simple test in tickets_controller_spec.rb
. This will make a GET
request to the index
action in the TicketsController
and check if the JSON response contains a value foo
that’s a string.
Pretty straightforward right?
You can do the same for post
, put
, patch
, and delete
requests.
Query Parameters, Request Body and Headers
The Airborne documentation doesn’t detail how to pass query parameters or a request body when testing a Rails Controller. All you have to do however is pass everything as a hash as the second argument.
describe 'POST create' do
it 'returns correct types' do
body = {'subject' => 'Hey', 'body' => 'Testbody'}
post :create, body
expect_status(201)
end
end
Or in the case of query/url parameters.
it 'returns correctly status filtered tickets' do
status_filter = Ticket.statuses.keys.sample
query = {'status' => status_filter}
get :index, query
expect_status(200)
expect_json('*', status: status_filter)
end
If you have to send header parameters too along with query parameters, all you need to do is merge
them into one hash and pass them as the second argument.
it 'returns correctly user filtered tickets' do
user_id_filter = User.includes(:tickets).where.not(tickets: {id: nil}).sample.id
query = {'user_id' => user_id_filter}
query.merge! auth_headers
get :index, query
expect_status(200)
expect_json('*.user', id: user_id_filter)
end
Working with devise_token_auth
devise_token_auth is my favourite gem for API authentication with rails and it provides a simple method to generate auth headers for your user; .create_new_auth_token
.
You’ll probably have to authenticate a lot of requests so it’s better to not have to write this code in every controller. We can DRY this up by adding to our spec/rails_helper.rb
file.
config.before(:all, type: :controller) do
@user = create(:user)
@user_auth_headers = @user.create_new_auth_token
end
I used factory_girl to create my user and you can do that however you like. What the above code does is simple, before all tests in a single controller it will create new authentication headers for your test users and you can pass that has as the second argument in your request to authenticate it.
it 'returns only tickets that belong to logged in user' do
get :index, @user_auth_headers
expect_status(200)
expect_json('*.user', id: @user.id)
end
Airborne API
Below are the things you should definitely check out in Airborne’s documentation.
Airborne provides the following to enable you to test the structure, values and data types of the elements in your API’s JSON response.
expect_json_types
- Tests the types of the JSON property values returned. You can find the types that can be tested here.expect_json
- Tests the values of the JSON property values returnedexpect_json_keys
- Tests the existence of the specified keys in the JSON objectexpect_json_sizes
- Tests the sizes of the JSON property values returned, also test if the values are arraysexpect_status
- Tests the HTTP status code returnedexpect_header
- Tests for a specified header in the responseexpect_header_contains
- Partial match test on a specified header
Usage examples can be found in the official documentation. It also has very good ways to handle testing array responses.
Furthermore, you can use path matching to test sub-properties within your JSON response.
Finally, here’s the source code to the example application I was building to sort of get familiar with Airborne, FactoryGirl & Rspec. It’s a pretty simple ticketing system API but you might find some gems there.
Get it? Gems?
That’s all for today. Hope this make your API testing a bit easier.