Matching Expectations

It’s summer time! So let’s go to an ice cream parlor and get some much needed refreshment. We break out our best faux-Italian accent and order “Una Tschokkolatta per fawor!” – but what do we get instead of that deep brown ball of chocolatey goodness in a crisp handrolled cone? A pink “unicorn” flavored monstrosity with marshmallow chunks in a paper cup. Another fine holiday memory ruined by faulty unit tests. Wait. What? You thought I wanted to tell you about my last holiday? That’s the blog post over there. Today, I want to talk about matching your expectations in unit tests.

Ice Cream!

So let’s set this up. First we need some ice cream:

Then we need some container to put the ice cream in:

… and finally, the reason for our troubles, a trio of ice cream vendors with their specific understanding of what the perfect ice cream should look like:

Tasting

So let’s define our expectations and test the three:

That is a nice little unit test, but can you see what we want to test here? It’s kind of hard for me, and I just wrote it. So what did uncle Bob tell us about things we just wrote? Look if they can be refactored to something better! So let’s first extract that test for color, and while we’re at it put the tests for container style and pieces into more eloquent methods:

There. Much better. Now let’s put in a bigger order. The famous “Tricolore” cup in the Italian national colors of green, white, and red, represented by pistachio ice cream with little crunchy pieces of pistachios, a sour ball of lemon (no fancy lemon skin zests here) and a soft pink strawberry ice cream made from real strawberries, so some soft pieces will be in there. And as we don’t trust our toddler to balance three scoops of ice cream on a cone before they melted, please put it in a cup – plastic please, he always crushes the paper ones…

That’s an awful lot of repetition. Can somebody please show what the differences between the tests for the scoops are? All I can see is the boilerplate.

Better. Now I see the differences much clearer. Just the color matching had to remain outside, because that’s a different assertion each time and we don’t want some switch-case-madness in our assertScoop method. And there is no way to pass matching logic around … or is there? We could write some nifty lambdas:

And if it was just ice cream, I would probably leave it at that and focus more on teaching princess Peach that not everyone wants fluffy unicorn ice cream. Or write some BuilderFactoryProviders to make it easier to serve ice cream. But I’m here to talk about tests and you haven’t left yet, so here we go: Run that last test.

What went wrong? I have no idea. let me look at line 113 of the test class.

There is a way to make the test failure tell me what really went wrong,

but who wants to write the same thing twice in prose and in code? And do I have to write that over and over again? Should I start collecting String constants and String builder methods along with my custom asserts? Or could I pack the knowledge, how I match and how I represent a mismatch, together in one object? Let’s call that object a Matcher. Oh wait. JUnit (or more precisely Hamcrest, conveniently packaged with JUnit) does that already. And provides base classes for my own matchers. Let’s write some:

Matchers

First, we create a matcher for colors, that hides that ugly r/g/b comparison behind a nice label:

And some constants for our reusable colors:

Of course we can reuse that matcher in another matcher, to pass a color matcher around like we did with the lambda before.

When using matchers, it is customary to use the more fluent assertions like assertThat(x, is(matcher)):

Yes, we can write a matcher for the cone as well:

…but the test code could look better. What does true even mean here? Hamcrest’s CoreMatchers offers many static methods to create it’s matchers, and so should we.

So now that we have a legible description of our matchers, maybe give our objects some toString methods so instead of this

we get this:

But why do we still pass those nulls to our ScoopMatcher. Or in the “Tricolore” case – do you think you would still know why you pass two "strawberry" Strings in? Couldn’t we use singular matchers for all our ice cream’s properties and just combine those? Glad you asked – CoreMatchers.allOf(Matcher<? super T>...) takes all matchers for the same object and makes sure they all match. But we don’t want to match IceCream, but just it’s properties. And getting those is something we can put into a base class of our own:

the type of the object’s property matched by the wrapped matcher */ public class PropertyMatcher<T, P> extends BaseMatcher { private Function<T, P> propertyGetter; private Matcher

propertyMatcher; private String propertyDescription; public PropertyMatcher(Function<T, P> propertyGetter, String propertyDescription, Matcher

propertyMatcher) { this.propertyGetter = propertyGetter; this.propertyDescription = propertyDescription; this.propertyMatcher = propertyMatcher; } @Override public boolean matches(Object item) { P property = getNullSafe(item); return propertyMatcher.matches(property); } @SuppressWarnings(“unchecked”) public P getNullSafe(Object item) { return item == null ? null : propertyGetter.apply((T) item); } @Override public void describeTo(Description description) { description.appendText(propertyDescription + ‘ ‘); propertyMatcher.describeTo(description); } @Override public void describeMismatch(Object item, Description description) { P property = getNullSafe(item); super.describeMismatch(property, description); if (propertyMatcher instanceof PropertyMatcher) { description.appendText(“\n because “); ((PropertyMatcher) propertyMatcher).getPropertyMatcher().describeMismatch(item, description); } } public Matcher

getPropertyMatcher() { return propertyMatcher; } }

…and use that to match our color and flavor:

…and create those matchers in their own methods:

We can even combine a matcher for our pieces and use that:

Yes, we could create and use the matchers for the pieces with their own methods, but that would be less readable in my opinion.

Now, let’s go even further and get rid of those list asserts and package the scoop scopes together:

Nearly there. Now let’s combine the container and the content matching and we can read the test like a sentence:

So finally we can see clearly, where Luigi went wrong:

Unfortunately, that’s not the whole truth, because Luigi’s lemon ice is too yellow, but Hamcrest’s allOf stops after the first failure, so you potentially stumble from error to error. Therefor it might be advisable to create your own combining matcher that collects all failed child matcher’s descriptions in the matches method and returns that in describeMismatch.

Conclusion: (When) should I use Matchers?

Matchers help with two things:

  • reusing test code
  • writing more readable tests

without sacrificing readability of error messages. It is a little more effort than just writing your asserts for the first test case and then copy’n’pasting from there, but once you have to edit and understand existing tests, it’s well worth the effort.
And yes, writing readable test setup is at least as important. When you can see at a glance what your test does and how it differs from it’s neighbors, you can spend more time on fixing your ice cream bugs then trying to understand your spaghetti code from yesteryear.

Leave a Comment

Your email address will not be published. Required fields are marked *

*