TLDR: RSpec offers a powerful match matcher that will allow you to leverage regular expressions to make non-brittle tests. Coding non-brittle tests that are robust and test behavior instead of implementation will save you maintenance time and lead to overall better quality code.
A common set of tests cases for any web app is checking that the correct message is returned upon user request. This can be anything from checking for “insufficient account funds” to “shoe size X not available.”
While software engineers like to think that the first message they code will be the last, these messages are often subject to change by the business group or users. The message might be too short, or too long or not clear in what it conveys. Because of this, a software engineer could end up changing this message many many times.
If these changes only effected updating a string then this would not be a lot of work. However, if you are having to update an exact assertion in a test suite as well, this work quickly multiplies and becomes a time suck. Every time you change the message, you must also update the test. This dependency makes your test very brittle.
Brittle tests are tests that check implementation instead of behavior. These tests, while sometimes useful, are not good for writing maintainable code. In the case of testing messages, we want behavior tests, not implementation test.
We don’t necessarily care if the message says “user has insufficient funds” or “User has insufficient funds” or “insufficient funds available.” As you can see, testing for the implementation of an exact string leaves us prone to tests breaking from many different derivations of the same message.
To avoid this implementation testing, we need to focus on behavior testing, or testing the intention of the code instead of the exact output. To do this with our messages we will need to pick out certain features of the message we’re concerned with. For these messages the keywords we need to check for are “insufficient funds.” If we are able to test for these words in our message, we can tweak the message as much as the business team wants without changing our tests.
But how do we test for specific words in our message?
How to Match with the match Matcher
RSpec gives us a great matcher to do this with match. Match is a regular expression matcher that accepts an argument of a regular expression and will check the value of the argument in the assertion’s expect against the argument of match. If the regular expression is able to match the expect string then the test will pass, otherwise it will fail.
At it’s most basic level you can pass simple strings into match to test against. The following match would return true:
Whatever we put between the “//“ will be treated as a regular expression we want to check against. This allows us to set a number of settings that are directly related to regular expressions. The following is a little more complicated:
Here we do a couple of things including using a “^” to declare that the string must start with “the dog”, a “*” or wildcard which says that anything can come after these first two words, and finally a “i” outside the “//“ to say that we want our check to be case insensitive.
Your regular expressions can be a lot more complicated, but this is a good base line for the power of match. If you do decide to do more, I find this regular expression checker to be very helpful (https://www.regextester.com/)
Using match to Check Availability
To see match in action in more real world examples we will look at how we might test the response to a user selecting an unavailable shoe size. For this we are modeling an e-commerce page for a new pair of sports shoes. If a user selects a shoe they should receive a message of “successfully added to cart” or “shoe size X is unavailable.”
Let’s write the successful cart add message first. The code for this matcher is more straight forward because we are not taking any information into account besides the message. If the shoe can be added to the cart then we inform the user of that, but our message does not need to know the shoe size or product in general.
Here we have a message that is part of our product_info controller called add_to_cart. This will make some queries to our database to check availability of the shoe and return a message depending on if we have the shoes or not. In this test we check that the return message includes “added to cart” and that the message is case insensitive.
By just checking the words “added to cart,” we make this test more robust as any different message such as “Successfully added to cart,” “Added to Cart” or even “Dear user, this product has beed added to cart” will work. Our coverage with this assertion is good and our tests are unlikely to break!
Our next test is a little more intense. Here we want to check that a unavailable message with the selected shoe size is returned. Again we want to make this non-brittle so we will only focus on checking for “unavailable” and the shoe size.
Here we use wildcards before and after our “unavailable” string to allow anything to be put around it. We also use string interpolation to retrieve the user input stored in the variable “shoe_size.” This will allow us to dynamically build our regular expression and correctly check for the size. Finally we use “i” on the regular expression to make the operation case insensitive.
While making this expression case insensitive is nothing special, the other two operators we use are incredibly powerful. First the use of wildcards “*” lets us check for any term possible as long as the terms are in a certain order. Following sentence structure rules, we can almost guarantee that certain words will always be in a specific order, regardless of what words or punctuation fall in between them.
The second powerful tool we have is the injection of string interpolation into our regular expression. This offers so much flexibility to us as our static expressions are suddenly dynamic. It’s not just that you are able to check that a unavailable message is returned, but you can use information from the user’s input to repeat to the user that you understood their request. Match offers so much power and flexibility through it’s following of regular expression rules along with its string interpolation injection.
When writing software tests, it is always best to focus on testing behavior over implementation. This not only saves developers the time of rewriting brittle tests, but will force you to really learn what you are expecting your code to do. Comment what your thoughts on match are and how you expect to use behavior driven tests in your future projects!