Testing an array of objects with RSpec have_attributes
After recently discovering RSpec’s --next-failure
option I’ve just happened upon the have_attributes
matcher which can help turn many expectations into a single, more readable statement.
In the past when checking an array of objects I’ve manually written out each expectation, something like this:
expect(items[0].id).to eql(1)
expect(items[0].name).to eql('One')
expect(items[1].id).to eql(2)
expect(items[1].name).to eql('Two')
But have_attributes
lets you check an object’s methods against a hash, so the above can be re-written as:
expect(items[0]).to have_attributes(id: 1, name: 'One')
expect(items[1]).to have_attributes(id: 2, name: 'Two')
Even better, have_attributes
can be combined with match_array
to get this:
expect(items).to match_array([
have_attributes(id: 1, name: 'One'),
have_attributes(id: 2, name: 'Two'),
])
In one particular case I also wanted to check that the correct class was being returned, which is simple as it’s just another method call:
expect(items).to match_array([
have_attributes(class: Foo, id: 1, name: 'One'),
have_attributes(class: Bar, id: 2, name: 'Two'),
])
The next thing I tried felt quite natural though I didn’t expect it to work:
expect(items).to match_array([
have_attributes(
class: Foo,
id: 1,
name: 'One',
price: have_attributes(
cents: 123,
currency: 'GBP',
),
),
have_attributes(
class: Bar,
id: 2,
name: 'Two',
price: have_attributes(
cents: 456,
currency: 'USD',
),
),
])
It turns out this almost exactly matches the examples from the docs — I guess I wasn’t paying much attention all those years ago when RSpec announced composable matchers.
Also on medium.com.