Using Specs² macro matchers for fun and profit

Shai Yallin | December 27th 2013 | Scala

At Wix, we make extensive use of the Specs² testing framework. It has become the standard tool for writing software specifications in our backend group, replacing JUnit, Hamcrest and ScalaTest.

We have always been highly fond of matchers, having adopted Hamcrest back in 2010, and our testing methodology has developed to heavily rely on matchers. We went as far as writing a small utility framework, back in our Java days, that takes an Interface and creates a matcher from it using JDK Proxy. For a class Cat with fields age and name, the interface would basically look like this:

  
interface CatMatcherBuilder extends MatcherBuilder<Cat> {  
  public CatMatcherBuilder withAge(int age);  
  public CatMatcherBuilder withName(String name);  
}  

Which then would’ve been used like this:

  
CatMatcherBuilder aCat() {
  return MatcherBuilderFactory.newMatcher(CatMatcherBuilder.class);
}

...


assertThat(cat, is(aCat().withName("Felix").withAge(1)));

As you can see, this involves a fairly large amount of boilerplate.

Moving to Specs², we wanted to keep the ability to write matchers for our domain objects. Sadly, this didn’t look much better written using Specs² matchers:

  
def matchACat(name: Matcher[String] = AlwaysMatcher(),  
              age: Matcher[Int] = AlwaysMatcher()): Matcher[Cat] =  
  name ^^ {(cat: Cat) => cat.name} and  
  age ^^ {(cat: Cat) => cat.age}  

What quickly became apparent is that this calls for a DRY approach. I looked for a way to create matchers like these automatically, but there was no immediate solution for implementing this without the use of compiler-level trickery.

The breakthrough came when we hosted Eugene Burmako during Scalapeño 2013. I discussed the issue with Eugene who assured me that it should be fairly easy to implement this using macros. Next, I asked Eric, the author and maintainer of Specs², if it would be possible for him to do that. Gladly, Eric took the challenge and Eugene joined in and helped a lot, and finally, starting with version 2.3 of Specs², we can use macros to automatically generate matchers for any complex type. Usage is fairly simple; you need to add the Macro Paradise compiler plugin, then simply extend the MatcherMacros trait:

  
class CatTest extends Specification with MatcherMacros {
  "a cat" should {  
   "have the correct name" in {  
     val felix = Cat(name = "Felix", age = 2)  
     felix must matchA[Cat].name("Felix")  
   }  
  }  
}  

It’s also possible to pass a nested matcher to any of the generated matcher methods, like so:

  
class CatTest extends Specification with MatcherMacros {
  "a cat" should {  
   "have the correct name" in {  
     val felix = Cat(name = "Felix", age = 2)  
     felix must matchA[Cat].name(startWith("Fel"))  
   }  
  }  
}  

For further usage examples you can look at the appropriate Unit test or check out my usage in the newly released Future Perfect library.

This would be the place to thank Eric for his hard work and awesomeness responding to my crazy requests so quickly. Eric, you rock.


By Shai Yallin
A seasoned software engineer focusing on JVM-based languages like Java and Scala; An avid advocate of clean code, continuous delivery and TDD.
Wix

Leave a Reply

1 comment

We are always looking for excellent people. Browse Jobs Here   

At Wix Engineering we develop some of the most innovative cloud-based web applications that influence our 80+ million users worldwide.

Have any questions? Email academy@wix.com.

Find out what’s coming up at Wix Engineering:

Subscribe to our newsletter for updates, events and more.