AssertJ, Stands On Its Own

Smell The Clutter

Java, with a healthy dose of testing code, has been my mainstay off and on for many years now.  JUnit and assertions have been a big part of my goto tools for most of that.  JUnit’s assertions felt underpowered from the start so, like most folks, I jumped on frameworks like hamcrest and fest. In some of code that’s old, or had a lot of hands in it, the result is a bit of a confused mess of all the possible frameworks.  Messy tests is as bad a code smell as any. Hamcrest hasn’t seen changes in ages and it appears fest is now abandon-ware so I decided it was time to deal with the smell.

Fest had been my preferred assertion framework and so AssertJ, the fest descendant with a still active community, was an obvious choice.  Some initial test usages proved it to be a cleaner fest. And so I began refactoring tests to use only AssertJ, removing JUnit’s, fest’s and hamcrest’s assertions.

Using AssertJ

AssertJ has a nice clean approach to assertions. It uses a factory method Assertions.assertThat() to create a type specific assertion. The type specific assertions offer fluent interfaces that are largely polymorphic.

Simple Example

static import org.assertj.core.api.Assertions.*;
...
@Test
public void shouldProvideAnExample() {
    String actual = "This is a test";
    assertThat(actual).contains("is");
    String[] actualArray = new String[]
               {"This", "is", "a", "test"};
    assertThat(actualArray).contains("is");
}

In the example above, assertThat() creates the appropriate type of assertion (String, Array) and contains, on a string looks in the string, and on the array searches the array.

Fluent API

One of AssertJ’s feature I like is its fluent API. In particular it’s cleaner than hamcrest’s approach IMHO. Consider, for example, determining if a string is between two values. There is a isBetween, but even barring that, the fluent approach looks like:

     assertThat(actual).isGreaterThan("bbb")
                         .isLessThan("ccc");

Where as hamcrest this would read (if there was greaterThan and lessThan):

     assertThat(actual, 
                 allOf(greaterThan("bbb"), 
                       lessThan("ccc")));

Clearly the two approaches are equally expressive, but I find the fluent syntax easier to scan.

Custom Conditions

import org.assertj.assertions.core.Condition;
static import org.assertj.core.api.Assertions.*;
...
@Test 
public void shouldBeEvenlyDivisibleBySix() { 
    Condition<Integer> evenDivBySix = new Condition<Integer>() { 
        @Override 
        public boolean matches(Integer value) { 
            return (value % 6) == 0; 
        } 
    }; 
    assertThat(12).is(evenDivBySix); 
    assertThat(8).isNot(evenDivBySix); 
}

In this example a condition was created that determined if an integer is evenly divisible by six and then used in assertions on integers.

Custom Assertions

Sometimes you’ve your own types with corresponding common assertions you’d like to apply to them. Perhaps you want to know if a Student instance isInMiddleSchool?
I won’t walk through all the interface implementations here, but the process is quite straight forward.

  1. Subclass AbstractAssert for Student and implement a isInMiddleSchool.
  2. Subclass Assertions adding a new assertThat factory method for your StudentAssert you created.
  3. Use your new factory and assertions as you’d always (i.e. assertThat(student).isInMiddleSchool())

That simple.

The Refactoring

The transition from fest was basically changing some imports and a few small tweaks (see Assertj Migrating from Fest).  JUnit’s assertions were largely an import change and a simple regex or two. Hamcrest was a bit uglier…. and more satisfying.

What Didn’t Work Out?

Everything worked out except a few glitches around Hamcrest.

  • Hamcrest has native bean support via getProperty that AssertJ doesn’t offer. Coded around those using AssertJ’s Conditions.  Probably could have found another Bean/POJO accessor class but was trying to reduce frameworks.
  • At least one mocking framework depends on Hamcrest for some features.  The dependence was minimal and didn’t introduce much clutter into the tests.

Slides Based on This Post