Use AssertJ for more explicit tests
We won’t discuss the merits of testing today, I’ll just suggest a library that helps them write differently and, according to me, makes them more comprehensible.
My Issue with Tests #
In my whole career, JUnit is the only framework I used for testing Java code (well, two frameworks, since I’ve used JUnit 4 and have more recently grown accustomed to Jupiter). Call me ignorant if you will, but I love this framework; it provides most of the bases I need for testing.
One detail that quite often comes back to bother me, though, is the writing of the assertions. Reading them is not always obvious, for several reasons:
- There are common mistakes, like inverting the “expected” and the “actual” parameters, which can make debugging a nightmare if you trust the code and don’t take a step back about what it’s supposed to do.
- It’s probably among the most procedural pieces of code we have to write in Java. Just a sequence of instructions.
- I’ve never known a developer (me included) who bothered to write messages for unverified assertions, and even comments are few (but I guess I could write an entire post on that).
I could go on, but you get the gist.
There are many ways to write tests that are more understandable. We may talk about Cucumber and Gherkin, but not today. Today, I’ll focus on fluent interfaces and more especially on AssertJ.
Fluent Interface to the Rescue #
Do you know what fluent interface is? It belongs to the object-oriented world and relies heavily on chaining method calls. For instance, I suppose the Builder pattern could be considered fluent interface:
1final Book gameOfThrones = Book.builder() 2 .title("A Game of Thrones") 3 .author("George R. R. Martin") 4 .series("A Song of Ice and Fire") 5 .build();
I like this approach better than what I knew before that. Calling setters one by one always seemed a bit heavy to me…
1final Book gameOfThrones = new Book(); 2gameOfThrones.setTitle("A Game of Thrones"); 3gameOfThrones.setAuthor("George R. R. Martin"); 4gameOfThrones.setSeries("A Song of Ice and Fire");
… and the all-args constructor also allow for immutable objects but can quickly become verbous and non-yieldy:
- You need to know the position of each argument: code reviews become harder without documentation or smart IDE.
- It declares either many variants or you might leave many fields
1// I don't know the publication year, leave empty 2final Book gameOfThrones = new Book("A Game of Thrones", "George R. R. Martin", null, "A Song of Ice and Fire");
This was a crude example of what can be done with a fluent interface. The Stream API is another.
However, there’s an interesting fact with several implementations I’ve seen: you get the feeling that, instead of writing a list of statement, you write a sentence, something that even a non-coder could read. AssertJ’s among these implementations.
Getting Started with AssertJ #
What would you get if you wrote your tests with AssertJ? Well, let’s see a few examples:
1assertThat(tyrion) 2 .isNotEqualTo(robert) 3 .isIn(lannisterFamily); 4 5assertThat(melisandre.getName()) 6 .startsWith("Mel") 7 .endsWith("dre") 8 .isEqualToIgnoringCase("melisandre"); 9 10assertThat(nedsChildren) 11 .hasSize(5) 12 .contains(robb, arya) 13 .doesNotContain(joffrey);
Now, imagine writing this with JUnit’s assertions and tell me which you prefer.
To get started with AssertJ, you need to import your dependency, so in Maven:
1<dependency> 2 <groupId>org.assertj</groupId> 3 <artifactId>assertj-core</artifactId> 4 <version>3.20.2</version> 5 <scope>test</scope> 6</dependency>
And then, in your test classes, replace JUnit’s assertions with one import:
1import static org.assertj.core.api.Assertions.assertThat;
assertThat is the main method, but several others are available.
From the documentation:
1import static org.assertj.core.api.Assertions.assertThat; // main one 2import static org.assertj.core.api.Assertions.atIndex; // for List assertions 3import static org.assertj.core.api.Assertions.entry; // for Map assertions 4import static org.assertj.core.api.Assertions.tuple; // when extracting several properties at once 5import static org.assertj.core.api.Assertions.fail; // use when writing exception tests 6import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; // idem 7import static org.assertj.core.api.Assertions.filter; // for Iterable/Array assertions 8import static org.assertj.core.api.Assertions.offset; // for floating number assertions 9import static org.assertj.core.api.Assertions.anyOf; // use with Condition 10import static org.assertj.core.api.Assertions.contentOf; // use with File assertions
Now, give AssertJ a spin and tell us what you think. As for me, I know that, since I discovered it, I have a tendency to replace all my JUnit assertions. You can see examples of JUnit 5 used with AssertJ in my Download proxy project.
Sources and references
AssertJ - fluent assertions java library, by AssertJ
Introduction to AssertJ, by Baeldung