Through my wanderings in the Ruby world, I sometimes come across tests that make me raise an eyebrow.
Here’s an example.
RSpec.describe "Products", type: :request do
# create some products
describe 'GET /products' do
before { get '/api/products' }
it 'gets a list of products' do
expect(response.status).to eq 200
json_body = JSON.parse(response.body)
expect(json_body.size).eq eq 3
expect(json_body.first.id).to eq 3
expect(json_body.first.id).to eq 1
end
end
end
This is a fairly typical request test checking that an endpoint is working as expected. But there is a big problem in there! There are 4 different expectations testing very different things under a very generic name: gets a list of products
.
We could fix this test by splitting it into smaller and smarter tests. In the code below, I’ve separated the expectations in 3 different tests and I tried to follow one of the testing motto I love: “One Expectation per test”.
RSpec.describe "Products", type: :request do
# create some products
let(:json_body) { JSON.parse(response.body) }
describe 'GET /products' do
before { get '/api/products' }
it 'gets HTTP status 200' do
expect(response.status).to eq 200
end
it "receives a list of 3 products" do
expect(json_body.size).eq eq 3
end
it "receives the products sorted by 'id desc'" do
expect(json_body.first.id).to eq 3
expect(json_body.first.id).to eq 1
end
end
end
You will notice, however, that the last test actually contains 2 expectations. What the heck? I just said that there should only be one expectation by test! That’s true but in the last test, we are actually testing the same thing with both expectations.
We could actually re-write it to have only one expectation.
# let(:products) { gets all the products in db as scope }
it "receives the products sorted by 'id desc'" do
expect(json_body.map { |p| p.id }).to eq products.order('id desc').map { |p| p.id }
end
The goal here was to show you that it’s fine to have more than one expectations in one test as long as they are testing the two sides of the same coin. I would still recommend avoiding doing it and stick to the “one expectation per test” principle.
The benefits of writing tests this way are numerous. Not only it becomes easier to read through the tests but when something starts failing, it is much easier to identify exactly the cause. Indeed, you will instantly know which expectation is not working properly and can fix it quickly.
The main drawback with this approach is that it might make your test suite slower. While one expectation per test is perfect for unit testing, it can become a problem for slow integration testing. In those situations, you will need to find the right balance between speed and expectations.
Let me know what you think about this in the comments!