A Guide To RSpec Built-in Matchers Part 1
Equality & Comparison Matchers
Click here for Part 2
Click here for Part 3
In this series of blogs, we will be going through some of RSpec’s most useful built-in matchers for writing assertions in examples. RSpec is known for its beautiful english-like syntax and matchers are no exception. Whether you’re completely new to testing in Ruby or a unit testing veteran, this guide aims to be a comprehensive introduction/review on some of the most common built-in matchers available to the RSpec library.
In this first part, we will be taking a look at two of the most commonly-used families of matchers: equality and comparison. Before we dive into those two groups however, let’s take a quick second to first talk about the to
and not_to
methods available in RSpec.
The to & not_to Method
This first method is a bit of an outlier within this blog, but is one of the most straight-forward to use. If you have had any experience with RSpec, chances are you have some familiarity with writing assertions using expect(...)
followed by a call to the to
method.
expect(number).to eq(9)
This creates a basic assertion expecting a variable called number
to have a value of 9
. The not_to
method is simply an alternative that operates similarly to a !
(bang) symbol within most programming languages: negating the boolean value it precedes.
expect(number).not_to eq("String")
In plain english: the value of the variable number
is not equal to the string "String"
. I find that a great way to practice writing tests is to always try and construct a not_to
method assertion that exactly mirrors what another to
method is checking for.
full_name = "Goro Majima"expect(full_name).to start_with("Goro")expect(full_name).not_to start_with("Kazuma")
Equality Matchers
Now, on to built-in matchers that come afterto
/ not_to
methods! The first set of three are equality matchers that check the value and identity of a subject.
eq & eql Matchers
When first learning RSpec, most developers like myself become very comfortable with the eq
matcher, that checks for value equality between a subject and its expectation. Using eq
, we can expect that 2
is eq
to 2
, the length of the string "Hello"
is eq
to the length of the string "World"
, or that "racecar"
is eq
to "racecar".reverse
.
eql
works very similarly to eq
, but it checks for value AND type equality. In other words, an integer cannot be eql
to its exact equivalent as a float, or a string containing the integer. An assertion must match the data type of its subject.
Most of the time, as long as you have a good sense of what your subject’s data type should be, eql
matchers provide an extra bit of security that your business logic is working the way it should be. el
in contrast, provides you with the flexibility of allowing type conversions (5 == 5.0)
.
equal & be Matchers
A different form of equality in Ruby is the concept of object identity. Whenever a reference type is initialized in RSpec, that instance is assigned a unique ID representing its place in memory. This means that any reference type in Ruby (arrays, hashes, or class instances) possess this unique identifier: an array will not be identical to an array with the same exact values unless they both reference the same position in memory.
RSpec’s equal
and be
matchers are aliases of each other (they perform the same function). Both will check a reference data type for object identity to make sure they point to the same object. Let’s use hashes as an example:
We still need to fill out our example logic, but here we have initialized two variables representing hashes using the let
keyword. What happens if we make an assertion using equal
to compare first_hash
to second_hash
?
Running this test results in the following error:
Failures:1) equal & be matchers checks for object identity
Failure/Error: expect(first_hash).to equal(second_hash)
expected #<Hash:2780> => {:a=>1, :b=>2, :c=>3}
got #<Hash:2840> => {:a=>1, :b=>2, :c=>3}
Compared using equal?, which compares object identity,
but expected and actual are not the same object. Use
`expect(actual).to eq(expected)` if you don't care about
object identity in this example.
Diff:
<The diff is empty, are your objects producing identical `#inspect` output?>
# ./spec/eql_spec.rb:18:in `block (2 levels) in <top (required)>'
The paragraph underneath expected/got
highlights the exact reason our test failed. Even though they contain the exact same key/value pairs, first_hash
is not equal
to second_hash
because their pointers are referencing two completely different hashes in memory. This can clearly be seen in the expected/got
lines as well.
In order to allow our test to pass, we need to add in a third variable using let
that points to the same position in memory as one of the other variables, and use eq
or eql
to compare our hashes otherwise.
Now all of our tests in this example group should be passing! Keep in mind that any call to equal
can be replaced with the be
keyword instead. Later we will see ways in which be
is used together with other types of matchers as well.
Remember these guidelines when using equality matchers:
eq
checks for value equality onlyeql
checks for value and data type equalityequal/be
checks for object identity
If you keep these rules in mind and use equality matchers as they are appropriate, you’ll be one step closer to mastering RSpec testing!
Comparison Matchers
The next family of built-in matchers in RSpec are known as comparison matchers. They are very straightforward and useful when comparison between a subject and expectation is required rather than equality. You will find that these matchers work very similarly to comparison operations in Ruby and other programming languages.
be Comparison Matcher
In order to implement comparison matchers, we make use of mathematical comparison operators together with the be
keyword.
expect(10).to be > 9
The argument encapsulated within theexpect
method should combine with what comes after be
to form a boolean statement. In this case, 10 is greater than 9. Any standard mathematical comparison can be used with the be
keyword.
One-liner Examples
As a side note, comparison matchers (and built-in matchers of any kind for that matter) can be condensed down with RSpec’s one-liner syntax. In this case, we can define an example and its expectation all on a single line using the is_expected
method inside a block.
Notice that 10
becomes the subject for each one-line example inside its block. As long as a group’s subject has a specific value, we can write our examples in this fashion.
To Be Continued
Next time, we will continue learning about RSpec testing by diving into predicate
and all
matchers, as well as other ways to use the be
matcher with truthy and falsey values. With equality and comparison matchers under our belt however, we are well on our way to becoming a seasoned RSpec developer. I look forward to traversing this series together with you all!