Elegant matcher for multiple method call expectations

arturmartsinkovskyi profile image Artur Martsinkovskyi ・2 min read

Boilerplate and his team
Recently, I was digging through some test code and found an annoying recurring pattern that is somehow clumsy and unsatisfying. I talk about such a case:

  expect(user.uid).to eq('hpqeqa12asdq1')
  expect(user.country).to eq('Germany')
  expect(user.street_number).to eq('9')
  expect(user.street_address).to eq('Somestrasse')
  expect(user.balance(from: 1.week.ago)).to eq(42000)

When you look at that wall of code you see some recurring pattern that is blazing before your eyes blocking you from seeing the essence. It is expect(user.***).to eq(... every time. This block does not bring much value into the expression and simply is boilerplate. Although, one might say that such test cases do not comply to the meaning of well-written specs because they do test multiple things at once, but this is not a case of bad code. It checks values that were most likely atomically inserted in one step, so having them tested separately is a waste of time and more boilerplate code that does not make understanding presumed assertions easier to grasp. I was thinking over the way to make such an expression more data-driven and less cluttered with noise. This is what I came up with as a result:

### Implementation ###

def expect_responses(object, **fields)
  fields.reduce(true) do |acc, (field, expectation)|
    acc && if expectation.is_a?(WithArguments)
              ) == expectation.expected_result
             object.send(field) == expectation

WithArguments = Struct.new(:arguments, :expected_result)

def with_arguments(*args, to_have_result:)
  WithArguments.new(args, to_have_result)

### Example ###

  uid: "hpqeqa12asdq1",
  country: "Germany",
  street_number: '9',
  street_address: 'Somestrasse',
  balance: with_arguments({from: 1.week_ago}, to_have_result: 42000)
) # => true

This one seems cleaner, and also I've added a way of passing arguments into the method. To make is easier to grasp for this post, this code returns a boolean value and uses simple comparison, in the real RSpec code it would run on expectations and use matchers to properly compare composite data structures. I tried to mimic common style of RSpec DSL, but I am not really sure on naming of this composite assertion expect_responses seems to be alright, but if you may suggest something better on this topic or any part of this post - leave a comment below. Would you find such an addition to RSpec worthy? Would you use it? Why? Why not?

Posted on by:

arturmartsinkovskyi profile

Artur Martsinkovskyi


Software Engineer from glorious Ukraine.


Editor guide