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.