Skip to main content

Literate Programming with jMock

Posted by tomwhite on May 11, 2006 at 11:10 AM PDT

We've been using jMock at our company for some time now. We've found it great for test driven development
and isolating our unit tests from the rest of the system more effectively. One aspect of jMock that stands out for me
is its idea of constraints. In fact, we've found this idea so useful that we always use the org.jmock.MockObjectTestCase base class
rather than junit.framework.TestCase, even when we aren't mocking anything out. This seems to have registered with the jMock development team, as they are planning to extract the constraints
into a separate project, codenamed Hamcrest (it's an anagram of "matchers").

We can use constraints to construct more flexible assertions.

assertThat(a, eq("3"));

is more readable and understandable than

assertEquals("3", a);

You can read it as "assert that a eq(uals) 3". (The only reason it is not equals is that it conflicts with Object's equals method.) This form makes the code more literate, that is, easier for humans to read, which is the main goal of Donald Knuth's idea of literate programming.

The assertThat form is very extensible too - you just write new constraints. We've grown a small library of constraints - a sort of DSL for unit testing.
The library contains constraints ranging from the simple conveniences isNull and isNotNull, to more complex collections constraints
such as includes which takes a variable number of objects (varargs) that the collection must contain:

assertThat(list, includes("peach", "pear", "plum"));

They are all easy to write.

Consequences

One thing we missed from JUnit assertions was the ability to control the failure message. This isn't often needed as the describeTo method of the constraint does a good job in most cases. There are occasions however when you need more context, such as when you have several boolean assertions in a single test and you want to distinguish them. For example, the following test

public void test() {
    boolean a = true;
    boolean b = false;
    assertThat(a, eq(true));
    assertThat(b, eq(true));
}

fails with a message that doesn't tell you if it is a or b that is false:

junit.framework.AssertionFailedError:
Expected: eq()
    got : false

We extended jMock's base test class org.jmock.MockObjectTestCase to take an overloaded version of assertThat that takes a new parameter called a Consequence. The test uses the otherwise method that creates a Consequence instance with the given failure message,

public void test() {
    boolean a = true;
    boolean b = false;
    assertThat(a, eq(true), otherwise("a should be true"));
    assertThat(b, eq(true), otherwise("b should be true"));
}

and fails more helpfully:

junit.framework.AssertionFailedError: b should be true

Literate Functional Tests

My colleague Robert Chatley, who worked on the jMock extensions described above, has recently taken the idea of literate testing further and applied it to functional (acceptance) tests. He has built a framework for testing a markup language for digital TV services, where as well as the assertions, the steps for driving the test are literate. Here is a sample:

public void testArrivalsWithUnambiguousOriginAndUnambiguousDestination() throws Exception {

    goToTheTrainArrivalsSearchScreen();

    enter(LONDON_PADDINGTON, into(textBox().labelled("Arriving at")));
    enter("Bristol Temple Meads", into(textBox().labelled("Calling at")));
    enter("1554", into(textBox().labelled("Arriving after")));
    select(option("Today"), from(selectBox().labelled("When")));

    clickOn(button("Select"));

    assertThat(theCurrentPage, has(title("Train arrivals after 15:54 today")));
}

Notice that you can't tell that it is testing a digital TV service. Those details are hidden behind the small collection of methods that allow you to perform actions: goTo() (called by goToTheTrainArrivalsSearchScreen()), enter(), select(), clickOn(); or extract data from the page: textBox().labelled("Arriving at"), button("Select").

Taking a tip from Nat Pryce, we can apply a neat trick and change Eclipse's settings so that punctuation is white, to make the code even more readable (but less editable)! (Steve Yegge had the same idea for Scheme.) This is what you get:

public void testArrivalsWithUnambiguousOriginAndUnambiguousDestination() throws Exception {

    goToTheTrainArrivalsSearchScreen();

    enter(LONDON_PADDINGTON, into(textBox().labelled("Arriving at")));
    enter("Bristol Temple Meads", into(textBox().labelled("Calling at")));
    enter("1554", into(textBox().labelled("Arriving after")));
    select(option("Today"), from(selectBox().labelled("When")));

    clickOn(button("Select"));

    assertThat(theCurrentPage, has(title("Train arrivals after 15:54 today")));
}

Truly literate.

Related Topics >>