Share and Enjoy

David Saff's blog of technological joys.

Thursday, April 26, 2007

 

assertThrownException

In my previous post, I described imposterization, a pattern for which I'm finding new uses. What can imposterization do for us? Let's look at testing for thrown exceptions.


In JUnit 3, this is the standard way of testing that a method throws an exception:


public void testIndexOutOfBounds() {
try {
new ArrayList().get(0);
fail("Should have thrown exception");
} catch (IndexOutOfBoundsException e) {
assertEquals("Index: 0, Size: 0", e.getMessage());
}
}


When creating JUnit 4, Kent and Erich recognized that this was a very commonly repeated pattern, and created an annotation-based way of testing for a thrown exception:


@Test(expected=IndexOutOfBoundsException.class)
public void indexOutOfBounds() {
new ArrayList().get(0);
}


There are advantages to each approach. The explicit try/catch block looks more like the expected client code, and allows assertions about more than just the type of the thrown exception. However, as with any repetition, an experienced user starts to glaze over the details of the test, and can miss important mistakes, such as:


public void testIndexOutOfBounds() {
try {
new ArrayList().get(0);
// OOPS! Now the test will pass even with no exception!
// fail("Should have thrown exception");
} catch (IndexOutOfBoundsException e) {
assertEquals("Index: 0, Size: 0", e.getMessage());
}
}


With imposterization, we can get the succinctness of the annotation-based approach while allowing arbitrary assertions about the thrown exception:


@Test public void indexOutOfBounds() {
IndexOutOfBoundsException e = new IndexOutOfBoundsException("Index: 0, Size: 0");
List emptyList = new ArrayList();
assertThrownException(is(e)).when(emptyList).get(0);
}


This now looks even less like expected client code, but it almost reads like English, and provides a one-line statement of the behavior I expect. Here, I'm using imposterization as a technique for building a mini-language, just as jMock 2 does for setting expectations. This kind of imposterization I might call syntactic imposterization: the impostor only appears in the test code, in order to refer to methods of the interface. I could then use the term semantic imposterization for uses in which the impostor is used in the same context that an original implementor would be used--for example, a mock object, or a capturing decorator from test factoring.

The expression is(e) uses Matchers from the hamcrest project, giving me a lot of power to express properties of the exception that's thrown.

When developing a mini-language using syntactic imposterization, it can be tricky to make the interface comprehensible. It would be wonderful to be able to say:


assertThat(exceptionThrownBy(emptyList.get(0)), is(equalTo(e)));


This would use the assertThat statement made famous by Joe Walnes, which I love, but unfortunately, the expressions that I'd like to talk about often have void return values, making it impossible to compile the above statement. Therefore, the verb must always go to the end, resulting in statements that sometimes read a little more like German, or perhaps Yoda. There's three ways I've noticed so far to get around this problem without making my natural language brain hurt too much:


  1. Dick and Jane style: in this style, I create a mutable object that remembers the methods called on it, and repeat the noun to finish the thought:


    MethodCall mc = new MethodCall();
    mc.calls(emptyList).get(0);
    assertThat(mc, will(throwException(is(e))));


  2. Fragment style: this is like Dick and Jane, only the noun is implicitly stored in global state or the host object, making the statements stateful. This is how JMock captures expectations:


    call(emptyList).get(0);
    assertThrownException(is(e));


  3. Subordinate clause style: this style, already shown in the first code example, puts the result before the verb, allowing everything to take place in one statement, if perhaps a little backwards:


    assertThrownException(is(equalTo(e))).when(emptyList).get(0);



Currently, I'm mixing the types of syntax imposterization in my tests, for asserting on thrown exceptions, as above; for identifying methods to be operated upon by my custom test framework:


FunctionPointer function = new FunctionPointer();
function.pointsAt(this).getStringReturnsA(null);
Object[] stubs = oldPopulator.stubsFor(function);


for identifying prerequisite tests:


onlyIfPassing(StubValueTableTest.class).cantAddSameValueTwice();


and for generating custom matchers based on observer method calls:


public static Matcher hasId(final String id) {
ViewReferencePropertyMatcher matcher = new ViewReferencePropertyMatcher();
matcher.mustMatch(id).getId();
return matcher;
}


However, I plan to try to coalesce a couple of these uses. I'm currently in the overuse phase* of syntax imposterization, and looking to bring my code back to its most readable state.

* Thanks to Martin Fowler for identifying this phase.

Comments: Post a Comment





<< Home

Archives

February 2005   June 2005   March 2006   August 2006   December 2006   April 2007   May 2007   January 2008  

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]