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
Using a regular expression for the body is a bit silly but works as an example. I’ll show you a really neat way to test JSON in the next part!

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
Writing expectations with rspec-its.

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
The first two aren’t shorter but they could be more readable.

Unfortunately these symbols can’t be used with rspec-its since the status codes in the response object are just Fixnums.

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
/spec/support/factories/user.rb

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
Using a factory in a spec. Check out factory_girl’s docs how to include the helper methods.

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
/spec/support/factories/user.rb (with traits)

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
Using traits in a spec. Here I’m using factory_girl’s helper methods.

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
Using blocks to set dynamic values in a factory.

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.


Related posts