AssertJ – The only Java assertion framework you need

Every language has its defacto testing framework. Java has JUnit, Ruby has Rspec, PHP has PHPUnit, etc. One of the things that you inevitably have to do when writing unit tests is assert what you expect to be true. I’d like to focus specifically on assertions in JUnit and explain why AssertJ is the best solution by contrasting the approaches that both provide.

Often times you write stanzas of ARRANGE-ACT-ASSERT that look like this. This code uses JUnit matchers for its asserts. We’ll use this code example repeatedly to show how AssertJ can provide a better developer experience from start to finish.

import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertThat;

    @Test
    public void a_list_works_properly() throws Exception {
        //Arrange
        List strings = new ArrayList<>();
        strings.addAll(Arrays.asList("Ryan", "Julie", "Bob"));

        //Act
        String removedString = strings.remove(0);

        //Assert
        assertNotNull(removedString);
        assertEquals("Ryan", removedString);
        assertThat(strings, containsInAnyOrder("Bob", "Julie"));
    }


    @Test
    public void serializes_to_disk() throws Exception {
        //Arrange
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        metaDatabase.addImage(Paths.get("fake").toAbsolutePath(), new Metadata(Arrays.asList(new Metatag("5", 111))));
        metaDatabase.serialize(byteArrayOutputStream);

        //Act
        MetaDatabase deserializedMetadatabase = metaDatabase.deserialize(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));

        //Assert
        assertEquals(deserializedMetadatabase, metaDatabase);
        assertNotSame(deserializedMetadatabase, metaDatabase);
    }

IDEs can help with autosuggesting AssertJ matchers, but cannot help with Hamcrest and JUnit matchers

Notice the four static imports at the top. One for each Junit supported matcher, and one for each Hamcrest supported matchers. This means that if you want to use a semantically meaningful matcher you have to know a priori, before you start typing your assert in your IDE, what the exact name of the assert class. The IDE cannot help you. You must also know which matchers are appropriate for comparing which classes.

Contrast this with AssertJ where all of the matchers come from the single static class Assertions so you only ever have one import statement no matter which classes you’re trying to assert expectations on. AssertJ is also sensitive to the type you pass into the expectation matcher. This allows you to use your IDE to find the exact matcher that you want without context switching to documentation or a Google search.

You can see this in action below. Here the IDE presents you specific methods off of the assertThat call which are applicable to the type String. Notice that you don’t see any methods here that have to do with measuring interger values.

Screenshot of IDE completion

However when we pass an integer into assertThat we see that the IDE is able to suggest a completely different set of assertions. You aren’t forced to memorize a large collection of matchers, instead the IDE can use code completion to serve up the applicable ones.

assertj.png

You can also make AssertJ aware of your own custom classes, and present specialized assertions just for them. There are also libraries to give you custom assertions that have already been created for Guava, Joda Time, JDBC Databases, Neo4J, and Android as well as many more.

AssertJ improves clarity by reading like a natural language which saves you time

Let’s see what it looks like to convert the first example to AssertJ. Note that the AssertJ author has provided several tools to seamlessly convert from JUnit assertions to AssertJ assertions.

import static org.assertj.core.api.Assertions.assertThat;

public class MetaDatabaseTest {
    private MetaDatabase metaDatabase;

    @Before
    public void setup() throws IOException {
        metaDatabase = new MetaDatabase(new FileWatcherService(FileSystems.getDefault().newWatchService()));
    }

    @Test
    public void serializes_to_disk() throws Exception {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        metaDatabase.addImage(Paths.get("fake").toAbsolutePath(), new Metadata(Arrays.asList(new Metatag("5", 111))));
        metaDatabase.serialize(byteArrayOutputStream);

        MetaDatabase deserializedMetadatabase = metaDatabase.deserialize(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));

        assertThat(deserializedMetadatabase).isEqualTo(metaDatabase);
        assertThat(deserializedMetadatabase).isNotSameAs(metaDatabase);
    }

    @Test
    public void a_list_works_properly() throws Exception {
        //Arrange
        List<String> strings = new ArrayList<>();
        strings.addAll(Arrays.asList("Ryan", "Julie", "Bob"));

        //Act
        String removedString = strings.remove(0);

        //Assert
        assertThat(removedString).isNotNull();
        assertThat(strings).doesNotContain("Ryan");
        assertThat(strings).contains("Bob", "Julie");
    }

A few things to note here. One is the single static import at the top. This is why your IDE can provide you with extra help. The other is the fact that the item you’re asserting on always comes first, and is clearly delineated by a matcher. This makes it crystal clear which value is the actual value, and which value is the expected value. Contrast this line from AssertJ

assertThat(deserializedMetadatabase).isEqualTo(metaDatabase);

with this line from JUnit where it’s completely unclear which one is the actual value and which one is the expected value.

assertEquals(deserializedMetadatabase, metaDatabase);

This may seem like a trivial distinction at first, after all both assertions will fail correctly regardless of the order. But watch what happens to the error message in JUnit when you mix up the expect and actual as in the example below.

assertEquals("this is what the system returned", "expected value to return");
org.junit.ComparisonFailure: 
Expected :this is what the system returned
Actual :expected value to return

 

Now as a developer you may waste significant time trying to figure out why your system output “expected value to return” when it was actually outputting “this is what the system returned”. False information like this severely damages a developer’s mental model of the program and forces them to suddenly reconstruct large portions of it to account for this strange behavior.

AssertJ has a bunch of cool features for filtering on collections and asserting on exceptions unavailable in JUnit

Soft assertions

Soft assertions allow you to write tests that don’t stop when they hit the first failure. Instead the test will execute as long as it only encounters soft assertion failures and will print them all out at the end of the test. This can be helpful when you have a large collection of single line asserts in a given test.

Exception assertion

Exception assertions give you a powerful set of tools to fully vet the exceptions being returned by your code. JUnit supports testing exceptions, but the syntax is really a step backwards in my opinion.

Here’s JUnit 4

@Rule public ExpectedException exception = ExpectedException.none();

@Test
public void example3() throws NotFoundException {
   exception.expect(NotFoundException.class);
   exception.expectMessage(containsString("exception message"));
   methodThatThrowsNotFoundException("something");
   // ... this line will never be reached when the test is passing
}

And here’s assertJ. Notice you don’t have to fiddle with any external @Rule classes, and you can execute arbitrary code within the lambda closure. This feature obviously requires Java 8.

@Test
public void testException() {
   assertThatThrownBy(() -> { throw new Exception("boom!"); })
      .isInstanceOf(Exception.class)
      .hasMessageContaining("boom");
}

Extracting type safe values from collections

AssertJ gives you the capacity to transform collections into other collections based on the attributes of the first one. It’s like a map function if you’re familiar with those. Below you can see how some assertions are made on the Stream of TolkeinCharacters before the race of each character is extracted, and then assertions are made on those races directly with the collections comparator contains().

Stream<TolkienCharacter> fellowshipOfTheRing = Stream.of(frodo, sam, pippin, boromir, legolas, gandalf, gimli);

assertThat(fellowshipOfTheRing)
   .contains(frodo)
   .doesNotContain(sauron)
   .extracting(TolkienCharacter::getRace)
   .contains(HOBBIT, ELF)
   .doesNotContain(ORC);

Conclusion

I hope you give AssertJ a try in your projects. I think it’s the best Java assertion framework out there and it’s always the first testing library I add to a Java project.

4 thoughts on “AssertJ – The only Java assertion framework you need

Leave a comment