Integration testing for Rails APIs part 2: Writing less code
In this second part I’ll show you some techniques that help you spend less time writing tests. Let’s take a look at alternate ways of writing expectations and how to generate test data using factories.
Writing more concise expectations
RSpec’s expressive syntax is great, but some commonly written expectations could be shorter.
Checking the status and body of a received HTTP response is a common task in integration specs:
context 'when the requested ID is invalid' do
let(:id) { -1 }
it 'returns not found' do
expect(response.status).to eq 404
expect(response.body).to match /not\s+found/
end
end
If you’re looking to make expectations like this shorter, rspec-its has got your back.
With it you can skip the it
block completely since the behaviour is described pretty well by the expectations:
# Don't forget to set the subject after RSpec.describe
subject { response }
context 'when the requested ID is invalid' do
let(:id) { -1 }
its(:status) { should eq 404 }
its(:body) { should match /not\s+found/ }
end
rspec-its is meant for situations where you’re testing an attribute. Note that the expectation should always make sense as a sentence so that RSpec can automatically infer the test description from the matcher.
If you think status codes aren’t readable enough, rspec-rails has a couple of matchers that should help you:
expect(response).to have_http_status(:success) # 200
expect(response).to have_http_status(:not_found) # 404
expect(response).to be_success # 2xx
Unfortunately these symbols can’t be used with rspec-its since the status codes in the response object are just Fixnum
s.
Manufacturing test data with factories
Integration testing usually requires all kinds of test data.
Rails gives you YAML-based fixtures by default, but populating the database with diverse data is a bit of a chore with them since you need to fiddle with ERB templates and YAML.
Fortunately the lovely people at Thoughtbot have come up with factory_girl, a library that allows you to write factories for your models.
Let’s assume that we have a simple User model set up that takes an email address and a password.
With factory_girl you could write a factory that manufactures instances of that User model:
FactoryGirl.define do
factory :user do
email '[email protected]'
password 'hunter2'
end
end
Using this factory in a spec is dead simple:
RSpec.describe 'GET /users/:id' do
let(:user) { FactoryGirl.create(:user) }
# or if you include the helper methods...
let(:user) { create(:user) }
# ...
end
At some point we might need to add support for multiple kinds of users, e.g. regular unprivileged users and special admin users.
We could do that by adding an enum attribute to the model called role
that can be set to either 'unprivileged'
or 'admin'
.
factory_girl then lets us define traits to modify the base factory:
FactoryGirl.define do
factory :user do
email '[email protected]'
password 'hunter2'
role 'unprivileged'
trait :as_admin do
role 'admin'
end
end
end
You can use traits by specifying them after the factory name:
RSpec.describe 'GET /users/:id' do
let(:user) { create(:user) }
let(:admin) { create(:user, :as_admin) }
# ...
end
Generating dynamic test data
It’s all well and good, but the values that we’re testing are looking a bit static, don’t you think?
Let’s improve that using the faker gem that generates fake data. It can generate almost anything you might want as a value!
You can specify a block in the factory to set the attribute dynamically:
FactoryGirl.define do
factory :user do
email { Faker::Internet.email }
password { Faker::Internet.password }
role 'user'
trait :admin do
role 'admin'
end
end
end
Now you’ll get different data for each new user created. This makes it more likely for you to be able to catch any unanticipated edge cases that might crop up with validation for example.
factory_girl has some other great features as well that are especially well suited for testing APIs but you’ll have to wait until the next part to find out more.
Any tips or thoughts to share? Leave a comment!
There are still many goodies to come. Subscribe to the newsletter below and I’ll let you know about them first.