Flexible JUnit assertions with assertThat()

Over time I’ve found I end up with a gazillion permutation of assertion methods in JUnit: assertEquals, assertNotEquals, assertStringContains, assertArraysEqual, assertInRange, assertIn, etc.

Here’s a nicer way. jMock contains a constraint library for specifying precise expectations on mocks that can be reused in your own assertion method (and that’s the last time I’m going to mention mocks today, I promise – despite the frequent references to the jMock library).

By making a simple JUnit assertion method that takes a Constraint, it provides a replacement for all the other assert methods.

I call mine assertThat() because I think it reads well. Combined with the jMock syntactic sugar, you can use it like this:

assertThat(something, eq("Hello"));
assertThat(something, eq(true));
assertThat(something, isA(Color.class));
assertThat(something, contains("World"));
assertThat(something, same(Food.CHEESE));
assertThat(something, NULL);
assertThat(something, NOT_NULL);

Okay, that’s nice but nothing radical. A bunch of assert methods have been replaced with different methods that return constraint objects. But there’s more…

h3. Combining constraints

Constraints can be chained making it possible to combine them in different permutations. For instance, for virtually every assertion I do, I usually find that I need to test the negative equivalent at some point:

assertThat(something, not(eq("Hello")));
assertThat(something, not(contains("Cheese")));

Or maybe combinations of assertions:

assertThat(something, or(contains("color"), contains("colour")));

h3. Readable failure messages

The previous example can be written using the vanilla JUnit assert methods like this:

assertTrue(something.indexOf("color") > -1 || something.indexOf("colour") > -1);

Fine, the constraint based one is easier to read. But the real beauty is the failure message.

The vanilla JUnit assert fails with:

junit.framework.AssertionFailedError:

Useless! Means you have to put an explicit error message in the assertion:

assertTrue(something.indexOf("color") > -1 || something.indexOf("colour") > -1,
"Expected a string containing 'color' or 'colour'");

But the jMock constraint objects are self describing. So with this assertion:

assertThat(something, or(contains("color"), contains("colour")));

I get this useful failure message, for free:

junit.framework.AssertionFailedError:
Expected: (a string containing "color" or a string containing "colour")
but got : hello world

h3. Implementing it

The simplest way is to grab jMock and create your own base test class that extends MockObjectTestCase. This brings in convenience methods for free (I’m still not talking about mocks, honest). If you don’t want to extend this class, you can easily reimplement these methods yourself – it’s no biggie.

import org.jmock.MockObjectTestCase;
import org.jmock.core.Constraint;

public abstract class MyTestCase extends MockObjectTestCase {

protected void assertThat(Object something, Constraint matches) {
if (!matches.eval(something)) {
StringBuffer message = new StringBuffer("nExpected: ");
matches.describeTo(message);
message.append("nbut got : ").append(something).append('n');
fail(message.toString());
}
}

}

Now ensure all your test cases extend this instead of junit.framework.TestCase and you’re done.

h4. Defining custom constraints

Creating new constraints is easy. Let’s say I want something like:

assertThat(something, between(10, 20));

To do that I need to create a method that returns a Constraint object, requiring two methods; eval() for performing the actual assertion, and describeTo() for the self describing error message. This is something that can live in the base test class.

public Constraint between(final int min, final int max) {
return new Constraint() {
public boolean eval(Object object) {
if (!object instanceof Integer) {
return false;
}
int value = ((Integer)object).intValue();
return value > min && value < max;
}
public StringBuffer describeTo(StringBuffer buffer) {
return buffer.append("an int between ").append(min).append(" and ").append(max);
}
}
}

This can be combined with other constraints and still generate decent failure messages.

assertThat(something, or(eq(50), between(10, 20));
junit.framework.AssertionFailedError:
Expected: (50 or an int between 10 and 20)
but got : 43

In practice I find I only need to create a few of these constraints as the different combinations gives me nearly everything I need.

More about this in the jMock documentation.

h4. Summary

Since using this one assert method I’ve found my tests to be much easier to understand because of lack of noise and I’ve spent a lot less time creating ‘yet another assertion’ method for specific cases. And in most cases I never need to write a custom failure message as the failures are self describing.

h4. Updates

  1. The matchers from jMock have been pulled out into a new project, Hamcrest.
  2. A follow up to this post shows some creative uses of matchers, and talks a bit about when you shouldn’t use them.
  3. JUnit 4.4 now comes with assertThat()!
  • Trackback are closed
  • Comments (28)
    • Shane Duan
    • May 13th, 2005

    Eye opener. I am adding them to our project now. Thanks!

  1. I think you should post this to the JUnit dev list. This is what JUnit 4 should have.

  2. This should definitely go into JUnit instead of JMock, in my opinion. Why? Because JMock is not an assertion library but a mock objects library. I would personally not want to download JMock just because I want to make assertions. That’s something JUnit should provide.

    • Rex Madden
    • May 14th, 2005

    Maybe JMock could split off the Constraint library?

  3. jMock constraints belong in jUnit

    Joe Walnes has written up the idea that jMock constraints should be used in jUnit assertions. It reads better and is more flexible. I’m glad he’s written up such a nice explanation because some of us have felt for ages…

  4. What a great idea. And it’s so simple.

  5. what about this Nunit version?
    using System;

    namespace xnUnit
    {

    class Constraint
    {
    public delegate bool EvalDelegate(object obj);

    public EvalDelegate EvalHandle;

    public bool Eval(object obj)
    {
    return EvalHandle(obj);
    }
    }

    class Assert
    {
    public static void AssertThat(object obj,Constraint con)
    {
    if (con.Eval(obj))
    {
    Console.WriteLine(“Pass”);
    }
    else
    {
    Console.WriteLine(“Failed!”);
    }
    }
    }
    }

    using System;

    namespace xnUnit
    {
    class Program
    {
    public static Constraint Between(int min, int max)
    {
    Constraint con = new Constraint();
    con.EvalHandle = delegate(object obj)
    {
    int value = Convert.ToInt32(obj);
    return value > min && value < max;
    };
    return con;
    }

    static void Main(string[] args)
    {
    Assert.AssertThat(3, Between(2, 4));
    }
    }
    }

  6. Update. Now addded to jMock and checked in.

    S

    • Anonymous
    • May 16th, 2005

    This is also in NMock2 (coming soon). Except that they are called “Matchers” not “Constraints”.

  7. This is great.
    I have adapted it to nunit and nmock constraints –> http://www.jroller.com/page/cenkcivici

  8. Hi.

    Interesting shift. I put something similar into SimpleTest (PHP), but called them Expectations. They don’t come out quite so cleanly in PHP because of the need to write $this for each method call, and the lack of namespaces leads to very long names.

    I am now pursuing a different angle with this though. SimpleTest has a crude web tester ripped off from JWebUnit. I am curently pushing the constraint/matcher idea down into this as well…

    class MyTest extends WebTestCase {
    . function testHomePageIsFriendly() {
    . . $this->get(‘http://my-site/’);
    . . $this->assertTitle(
    . . . . new WantedPatternExpectation(‘/welcome/’));
    . }
    }

    Combined with an XPath expression you could deliver a content match directly to the point of interest in the web page.

    yours, Marcus

    • fede
    • May 18th, 2005
    • Anonymous
    • May 19th, 2005

    Here’s how it looks in NMock-2 (real test from the codebase)

    [Test]
    public void ReturnsCloneOfPrototypeObject()
    {
    ICloneable prototype = ACloneableObject();
    IAction action = new ReturnCloneAction(prototype);

    object result = ReturnActionTest.ResultOfAction(action);

    Verify.That(result, !Is.Same(prototype));
    }

    Mmmmm… operator overloading goodness!

  9. There is a problem with these assertions. See some comments in my blog at http://www.jroller.com/page/eu/20050516#resolving_local_variable_name_passed

  10. This is truly a great idea!
    I especially like the fact that jMock constraint objects are self describing.

    Congratulations!
    -sascha :-)

    • Steve Conover
    • June 2nd, 2005

    Joe –

    Great idea. What I’d like to do is be able to do some transformation on the actual for the exception message.

    For instance, to replace ArrayAssert.assertEquals, with something like “contentsAre”:

    public void testArray() throws Exception {
    assertThat(new String[]{“a”, “b”}, contentsAre(“x”, “y”));
    }

    private Constraint contentsAre(final Object… expectedContents) {
    return new Constraint() {
    public boolean eval(Object actual) {
    return Arrays.equals(expectedContents, (Object[]) actual);
    }

    public StringBuffer describeTo(StringBuffer buf) {
    return buf.append(Arrays.asList(expectedContents));
    }
    };
    }

    Results read like:
    junit.framework.AssertionFailedError:
    Expected: [x, y]
    got : [Ljava.lang.String;@14693c7

    What I’d like to do is somehow apply my same Arrays.asList transformation to the actual array so the message reads better.

    Suggestions?

  11. Evening Joe,

    Interesting and informative post. I’m currently working on a Open Source project which is similar, but not focussed on JUnit, but rather business rules…

    In essence, it handles “low level” validation, auditing, generating messages (for log messages and for exceptions) and generating correct exceptions (and even wrapping them in another exception (if for instance an exception happens in a DAO, creates an exception and wraps it as s DataAccess_Exception).

    Here’s what we’d like to avoid (in some business component):

    public class PositionalThing extends Thing implements Positional {
    private static Log log = LogFactory.getLog(PositionalThing.class);
    private static ThingDAO dao = DAOFactory.getDAO(ThingDAO.class);
    public static final int MIN_X_POSITION_VALUE= 0;
    public static final int MAX_X_POSITION_VALUE = 255;

    public void setXPosition(Integer xPosition) {
    if (xPosition == null) {
    String message = Null Position passed in for xPosition;
    log.error(message);
    throw new InvalidDataException (message);
    }
    If ((xPosition.intValue() MAX_X_POSITION_VALUE)) {
    String message = xPosition + not in Range 0-255 ;
    log.error (message);
    throw new ValueOutOfRangeException (message);
    }
    dao.setXPosition(xPosition);
    }
    }

    rather, we do this:

    public class PositionalThing extends Thing implements Positional {
    private static Log log = LogFactory.getLog(PositionalThing.class);
    private static ThingDAO dao = DAOFactory.getDAO(ThingDAO.class);
    public static final WholeNumberRangeRule X_POSITION_RULE =
    new WholeNumberRangeRule(X Position, 0, 255);

    public void setXPosition(Integer xPosition) {
    dao.setXPosition(X_POSITION_RULE.validate(xPosition, log));
    }
    }

    Anyways, I’ve implemented it as a part of a larger project I’m working on. It’s very useful for making the code more readable and understandable. (less “noise” as you say) I did not realize JMock had these constraints classes, however they will be a great addition to the framework.

    Thanks,

    Eric

    • Nate Austin
    • June 14th, 2005

    That looks great. It provides the readability of jMock with type safety. One minor syntactic modification I’d make is to have the or, and, xor, etc. operators work on the Constraint object itself. In other words, instead of
    assertThat(something, or(contains(“color”), contains(“colour”)))

    You would have the much more English readable

    assertThat(something, contains(“color”).or(contains(“colour”)))

    This will be going into my project as well.

    Thanks for the idea.

    -Nate

  12. Beautiful! Thanks, Joe. I blogged about this (with attribution, of course) a while back after Matt Raible mentioned it, but I forgot to drop you a note to mention that fact.

  13. Collection closure method

  14. Collection closure method

  15. Collection closure method

  16. Collection closure method

  17. Why isn’t this in JUnit-addons or some other package yet?

  18. Hello!
    After having read about it here, we implemented this great idea in the Java virtual mocking library rMock (http://rmock.sf.net).

    We extended the idea by adding the ability to build expressions of constraints as well.
    Example:

    public void testExpression() throws Exception {
    assertThat( “Hello”, is
    .containing( “ello” )
    .and( is.containing( “Hell” ) ) );
    }

    rMock has several other very nice features as well. Please, check it out to see if it matches your desires. We would be happy to get your feedback!

    Cheers
    Daniel, on behalf of the rMock team

    • Scott Mitchell
    • March 1st, 2006

    Joe, silly question. Would it be possible to use static imports in some way to work around the requirement to extend your MockObjectTestCase? Are any of the assertion methods depending on internal state of the class itself? I’ve thought a couple of times that static imports might be a usable as a poor man’s mixin, and this just hit me as maybe a place where it would be useful.

  19. Hamcrest 1.0

    An unexpectedly popular feature of jMock has been its library of Constraints. A constraint is a very simple object: it returns a boolean indicating if it matches another object and can describe itself. People have found this very useful….

  20. Hamcrest

    Hamcrest release 1.0 is now available. It allows you to write flexible assertions in your unit testing framework of choice.

Comments are closed.
%d bloggers like this: