Sam Starling
12 February 2013

Custom Matchers in ScalaTest

The documentation for ScalaTest is good, but I found that it lacked a little bit when it came to talking about custom matchers. As such, I thought it’d be useful to write up the approach I took recently when writing one.

Why use custom matchers?

My main motivation for using custom matchers was that they can make tests far more readable. At work, we deal with RDF a lot – and for a few reasons, we use a Java library to deal with it. Before using custom matchers, our tests had to jump through a few hoops:

it("parses the type of a Creative Work") {
  // fetch some RDF
  // convert it into a model
  // pull out a specific attribute
  // make an assertion on that attribute
}

That ended up being quite a lot to parse in one test – so it made sense to create a custom matcher.

Example

For simplicity, I won’t carry on with the RDF example. Instead, we’ll write a custom matcher that checks the ending of a string. In your test, you can use a matcher like this:

it("ends with the word 'mat'") {
  "The cat sat on the mat" must endWith("mat")
}

Elsewhere, you then define your custom matcher as a case class and use a def to instantiate a new instance of it, like this:

case class EndsWithMatcher(suffix: String) extends Matcher[String] {
  def apply(word: String): MatchResult = {
    val result = word.endsWith(suffix)
    MatchResult(result,
      word + " did not end with " + suffix,
      word + " ended with " + suffix + " but it shouldn't have")
  }
}

def endWith(suffix: String) = EndsWithMatcher(suffix)

There are a few key parts to what we just did:

  1. The constructor of the matcher. This takes the arguments passed to the ‘right’ of our custom matcher – in this case, the suffix. Since it’s a case class, we’ll have access to the suffix in our apply method.
  2. The apply method This method performs our actual assertion. In this case, we’re using Scala’s built in endsWith method for String objects. Our checking needs to return a boolean, which is then wrapped up in a MatchResult.
  3. The MatchResult return value The MatchResult takes a boolean result, and two error messages. The first one is for when we expected the result to be true, but it turned out to be false. The second error message is used when the result was true, but we expected it to be false. This second case comes into play when we use a negated matcher (ie. must not endWith).
  4. The method which instantiates our case class. Finally, so that we can use the word endWith in our tests, we define a method which instantiates our EndsWithMatcher with the correct arguments.

Done

And that’s pretty much all you need to know to be able to write your own custom matchers in ScalaTest, just like you’d do using rspec.