A Guide To RSpec Built-in Matchers Part 3
Miscellaneous Matchers for Arrays & Strings
Click here for Part 1
Click here for Part 2
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 the first two parts of this guide, we looked at some of the most often-used families of RSpec matchers, ranging from equality and comparison matchers to predicate, all, and be matchers. In part 3, we will break from this convention of grouping individual methods together and instead tackle a wide variety of different matchers that can be used with certain types of objects such as arrays and strings.
change Matcher
To start things off, the change matcher looks for mutable state within a block of code and checks to see what alterations occurred. In order to achieve this function, we need to pass in a Ruby block before and after the change
invocation as well as other optional methods such as from
and/or to
, and the by
method after the block.
For example, if we take an array as our subject and push in an additional element, we should expect the length of our array to increase by 1. The basic formula for passing in the change method to an expectation is as follows:
expect { my_change_operation }.to change { my_object_attribute }
Any additional methods listed above would be invoked after the second block containing the attribute expected to change. Here is one way we can implement an example group checking for changes to an array:
The first block in our example’s assertion passes in an operation to be performed on the object (subject.push), and the second block identifies the attribute we want to check for changes. Finally in this example, we also use the from
and to
methods together to further compare the changes in value made to our attribute.
What if our subject suddenly starts with one additional element before our tests run? This example would now fail because the starting length of our array is no longer accurate. A more flexible alternative to using from/to
is the by
method. This accepts a positive number to represent an increase in value and a negative number to represent a decrease.
The change matcher takes a few more steps to be executed compared to other matchers, but it can be a great tool to test for mutations within a Ruby object.
contain_exactly Matcher
One of the more specialized methods featured in RSpec is the contain_exactly matcher. It gives us a way to test all values inside an array without accounting for order.
To pass our test specs with contain_exactly
, we need to pass in every single element contained in the array as comma-separated arguments.
expect(my_array).to contain_exactly(1, 2, 3)
In the example above, we expect my_array
to contain exactly three elements: 1, 2, and 3 (in any order). If the arguments passed into contain_exactly
omit certain values in my_array
, or if extra arguments not included inmy_array
are given, our example would fail. Here is another example that tests an array containing two string values.
Remember, contain_exactly
is never concerned with order, so our specs will pass as long as the array contains all arguments given.
start_with & end_with Matchers
This pair of matchers are used to test the order of collections, including arrays as well as strings. start_with
specifies whether a collection starts with a given set of chars or elements and end_with
does the same for the last part.
expect(my_array).to start_with(1, 2)
expect(my_string).to end_with('world')
Any number of chars or elements can be passed in as arguments, but contrary to contain_exactly
, order matters in either case. Here is another example group using both methods with strings and arrays.
Given the nature of these matchers, it is apparent we cannot readily use them for unordered reference types such as hashes or instance objects. However, the last matcher we will look at can be used with any collection type in Ruby.
include Matcher
RSpec’s include
matcher works almost identically to its predicate method counterpart in Ruby: include?
. We can use it to check whether a string contains a set of characters, whether an array contains specific elements, and even whether a hash contains particular keys, values, or both.
expect(my_array).to include(1, 2)
expect(my_string).to include('ello')
expect(my_hash).to include(:a => 'Abigail')
Think of include
as a more flexible version of RSpec’s contain_exactly
. It can check for any number of values within the assertion and passes as long as those values are present in the collection being tested.
Let’s write some example groups testing strings, arrays, and hashes using the include
matcher. There are a lot of assertions inside each example, so be sure to carefully examine each example group subject, the values being checked for inclusion, and some of the different Ruby syntax being used.
As with the earlier parts of this series, I encourage you to play around with and test each RSpec matcher as you learn them. Experiment with your own examples and assertions. Practice using these matchers together with the to
keyword as well as not_to
. Try to remember which matchers accept standard arguments and which ones require you to pass in Ruby blocks.
To Be Continued
We are nearing the end of our journey through RSpec’s built-in matchers. In this blog, we covered some of the most useful matchers for strings and arrays outside of equality, comparison, and predicate matchers.
Next week, in the fourth and final part of this blog series, we will be concluding with more miscellaneous matchers used with special Ruby objects like error messages and classes.