RSpec is one of my favorite tools. I have literally fallen in love with this fantastic BDD library, especially with its second version. While using RSpec I realized it teaches me how to write tests. Yes, exactly - learning RSpec DSL, its syntax and structure of spec examples you actually learn the best practices in writing tests. RSpec, despite many built-in matchers, comes with a DSL for defining your own, custom matchers. It’s so easy that you’re not gonna believe this.
Basics
In RSpec matchers are nothing but methods available in the context of an example. You use them to make sure that a given expectation is met. There are many matchers that come with RSpec for instance here is how you can use the respond_to matcher:
1 2 3 | |
It’s so clean and beautiful that I probably don’t have to explain what this piece of code does, right?
Tip: When you call describe with a class as an argument, RSpec will automatically create an instance of that class and make it available via subject method. Subject is also the default context of an example block. That’s why we don’t have to write “subject.should respond_to(:gsub)”, because by default “should” or “should_not” is called on the subject.
Alright, for a list of available matchers check out the official docs. Let’s focus on writing our own matchers. If you’re wondering why you would need to do that, let me show a simple example of a User model spec:
1 2 3 4 5 6 7 8 9 10 11 | |
Now, you probably can imagine that almost identical code could be used in many other cases for many other model classes. Those 6 lines of code can be written as 1. You just need a custom matcher.
Defining a custom matcher is simple. Let’s start with a basic one that checks if a given model instance has validation errors:
1 2 3 4 5 6 | |
And we can use it like that:
1 2 3 4 5 | |
It covers only the first expectation, but it’s a good starting point.
Chaining
The second expectation in the example is to see if the correct validation error message is set. It’s possible to run matchers in a chain so let’s see how we can implement chaining in our custom matcher:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
It’s really that simple. Now the matcher checks two things and returns true only if the message exists and if it matches the expected one.
Let’s use it:
1 2 3 4 5 | |
Nice! One line instead of six. But that’s not everything, with a failing spec failure messages might look like that:
1 2 3 4 5 6 7 8 9 10 | |
It’s automatically generated by RSpec based on the matcher name. It’s ok, but notice that it won’t tell us if the error message was incorrect. That’s why we need to set custom failure messages.
Custom failure messages and it’s done!
It is really recommended to use meaningful failure messages. We need to set 2 types of them, first one for “should” and second one for “should_not” expectations. The idea is that if there is an error and the message is not correct, we need to show that information in the failure output.
So, our complete matcher looks like that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | |
Now if we run our example and it fails because an error message doesn’t match the expectation, we will get following failure message in the output:
1 2 3 4 5 6 7 8 9 10 11 | |
Summing up
As you can see implementing custom RSpec matchers is easy trivial and it’s a highly recommended practice. There are plenty of use cases where you want to write custom matchers. It makes your specs clean and even more readable and what’s most important it keeps your spec’s code DRY and extendable.
Here are some resources if you want to learn more: