---
title: Chain RSpec Matchers for Improved Test Readability
teaser: 'Improve test readability by leveraging RSpec''s matcher composition to make
  multiple assertions with a single matcher.

  '
tags: web,rspec,ruby,rails,testing
author: Josh Clayton
published_on: 2017-03-09
---

I've recently journeyed back into the world of Rails development after an
extended stay in more functional languages. One aspect of Ruby that always
delights me is its focus on readability. With this focus, along with the
ability to [fold] certain structures, I set out to improve a wordy test.

[fold]: https://thoughtbot.com/blog/derive-inject-for-a-better-understanding

The original test looked like this:

```ruby
select_multiple_from "Which of these apply to you?", [
  "I read the New York Times every day",
  "I read the Washington Post every day",
]

click_on "Submit"

expect(page).to have_css("dd ul li", count: 2)
expect(page).to have_css("dd ul li", text: "I read the New York Times every day")
expect(page).to have_css("dd ul li", text: "I read the Washington Post every day")
```

The `select_multiple_from` handles the multi-select:

```ruby
def select_multiple_from(from, options)
  options.each do |option|
    select option, from: from
  end
end
```

The three assertions felt a bit redundant, but necessary: I wanted to ensure
form submission captured only the items chosen (by verifying length of items in
the list) and that they showed up correctly.

When I write any Ruby, most especially when I'm writing acceptance tests, I
favor focusing on readability and working at a [single level of abstraction]; I
came up with:

```ruby
expect(page).to have_multiple_choice_responses(
  "I read the New York Times every day",
  "I read the Washington Post every day"
)
```

[single level of abstraction]: https://thoughtbot.com/blog/acceptance-tests-at-a-single-level-of-abstraction

This encodes the notion that these two items, and only these two items, should
show up in the list. While there are multiple assertions we're covering here,
it's possible to use a feature in many of RSpec's matchers to chain assertions:
[`and`]. It was introduced in the [`RSpec::Matchers::Composable`] module in
[RSpec 3] that many of the Capybara matchers, including `have_css`, have
available.

[`and`]: http://www.rubydoc.info/github/rspec/rspec-expectations/RSpec/Matchers/Composable#and-instance_method
[`RSpec::Matchers::Composable`]: http://www.rubydoc.info/github/rspec/rspec-expectations/RSpec/Matchers/Composable
[RSpec 3]: http://rspec.info/blog/2014/01/new-in-rspec-3-composable-matchers/#compound_matcher_expressions

As a first pass, we could write this matcher as such:

```ruby
def have_multiple_choice_responses(item1, item2)
  have_css("dd ul li", count: 2).
    and(have_css("dd ul li", text: item1)).
    and(have_css("dd ul li", text: item2))
end
```

There are a few problems here, the most notable that it's tightly coupled with
the number of items. Let's make this more flexible with Ruby's
[`Enumerable#inject`]:

```ruby
def have_multiple_choice_responses(*args)
  count_matcher = have_css("dd ul li", count: args.length)

  args.inject(count_matcher) do |matcher, text|
    matcher.and(have_css("dd ul li", text: text))
  end
end
```

[`Enumerable#inject`]: https://ruby-doc.org/core-2.4.0/Enumerable.html#method-i-inject

`Enumerable#inject` is called on our array of items we're expecting, with a
starting matcher (`count_matcher`), and for each iteration, extending that
matcher with `and` to layer in an additional assertion. This allows the matcher
to grow and have explicit expectations around the number of items present, as
well as their content. This doesn't assert order, but would be possible using
`each.with_index(1)`:

```ruby
def have_multiple_choice_responses(*args)
  count_matcher = have_css("dd ul li", count: args.length)

  args.each.with_index(1).inject(count_matcher) do |matcher, (text, offset)|
    matcher.and(have_css("dd ul li:nth-of-type(#{offset})", text: text))
  end
end
```

(Note that the `nth-of-type` pseudo-selector starts at an index of `1` instead
of `0`)

With this refactoring, we've achieved what I'd consider a readable matcher
which hits all the assertions necessary, and hides some of the specific CSS
selectors behind a well-named method. Success!
