RSpec Refactoring Patterns: Extract Matcher

If forced to pick only one technique for refactoring code, I'd go with Extract Method. And if I were made to choose similarly for refactoring specs, it’d be Extract Method’s descendant Extract Matcher.

Motivations

You’ve got one or more expectations verifying state, but its unclear from the level of abstraction what the intention of these expectations is. By extracting well-named matchers, you can clarify the intention and so make the spec easier to understand.

Another motivation for Extract Matcher is to reduce duplication of repeated expectations or groups of expectations.

Example

A feature spec contains an expectation for checking the flash message after a user logs in:

expect(page).to have_css ".notice", text:"You're logged in"

For a developer familiar with Capybara, RSpec, and CSS, this expectation is fine, its reasonably clear what its testing. It reveals the intention to "assert the user successfully logged in". If there's any issue with it, it is that plainer language could demonstrate the intention without the noise of a CSS selector.

Here's the first attempt at muting the noise by extracting a have_flash matcher.

have_flash is one level of abstraction higher than have_css as its language is one step closer to the language a user might use when thinking about how to tell if they are logged-in, and one step farther away from the CSS language the developer and web browser use to produce the page.

Can we get any closer to the language a user viewing our web page would use to make the test clearer?

Here's a couple alternatives for how the level of abstraction could be brought closer again to the user, and further away from the technical implementation of the page:

In this case is the readability benefit of moving to higher and higher levels of abstraction worth the extra effort? The returns on readability are diminishing but still there. This is one for you to decide based on your own style and experience in the moment (or just toss a coin, there's not much to choose between them).

I want to show you one more example of extracting a matcher to demonstrate how you can use it to group multiple expectations into a single, more intention-revealing expectation: