I Actually Enjoy Writing Java Tests Now That I’ve Learned These Tips

Discover how to verify complex objects with concise, fluent, and meaningful assertions

Photo de JESHOOTS.COM sur Unsplash

To verify the values of an object with Junit, we use assertEquals() statements.

https://medium.com/media/625b6f72dd0010f8f67def6c5ddbeca8/href

That’s fine to verify 3 or 4 properties, but what if we have 15? with complex nested objects? It quickly becomes a chore to write, and then, it makes for cumbersome reading.

That’s unfortunate because writing tests brings many valuable benefits. Sure, automated tests catch regressions. But there’s more.

As Steve Freeman and Nat Pryce explained in Growing Object-Oriented Software, Guided by Tests, when some functionality is difficult to test, it’s an early warning that the code will be difficult to maintain and evolve. Perhaps it has too many dependencies (high coupling), or it doesn’t have clear responsibilities (separation of concerns). Writing tests leads to improved design, without overengineering.

Another valuable benefit is that clear, concise, and organized tests really help other developers to understand how a component works. Tests that are hard to read fail to provide this documentation aspect.

For all these reasons, it’s worth investing some time to make tests easy to write, and to read. We should eliminate anything that makes testing tedious, like these long sequences of assertEquals().

Even more frustrating, these verifications are far easier to write and read in Javascript, with the Chai Assertion Library.

https://medium.com/media/41b9fba1d4baade04465fefd2e2a2964/href

The JS version is more concise. It’s straight to the point.

So how can we eliminate all this noise in the Java version, and make it as simple as in JS?

Reflection with AssertJ

What exists in the Java world that is close to Chai’s .deep.include()? The AssertJ library offers fluent assertions that compare values by reflection.

https://medium.com/media/0c23b3c6e6d9a5d8a71b0c00f1aa43d5/href

This is a 2 steps approach (initialization and comparison). It’s quite hard to follow and it doesn’t read well, but that’s just a starting point.

One good point to note: when this test fails, the error message shows precisely which property doesn’t have the expected value.

java.lang.AssertionError:
[...]
field/property 'position.end' differ:
- actual value : "D6"
- expected value: "E6"
[...]

This detailed feedback really helps when the object has many properties.

Let’s see how we can make this code smoother with a Builder.

Inline definition with the Builder pattern

To make the verification more concise, we can use a Builder object. The Builder pattern allows to construct an object using a fluent chain of method calls.

https://medium.com/media/ee89ef7671a3bf1605117fc186fec342/href

The new version is much better. It reads more naturally than the 2 steps approach. It’s also safer than calling a constructor with tons of parameters.

What does the Builder look like in practice? It’s a bit long — as is often the case in Java — but most of the code can be generated automatically.

https://medium.com/media/c64c61cd4803ded7068e978a54e09562/href

There are several options to generate this Builder code.

First, there is the @Builder annotation from Lombok. It dynamically generates the Builders at compile time (I oversimplify, but you get the idea). Unfortunately, with this approach, you can’t overload Builder methods like I did for the Ship Builder.

Alternatively, you can ask your favorite AI agent to generate Builders. Or you can use the Builder generator IntelliJ plugin, which does a great job and is more frugal (🌱).

Concise API

Thanks to the Builder, the assertion is much simpler, but it stays way too long.

https://medium.com/media/2d2edeecdf9a6f11627e5d68fe57aa41/href

Clearly, we don’t want to write this elephant-sized assertion in every single test!

Instead, we can create a kind of alias for this pair of instructions: a custom assertion that is shorter and nicer to use.

https://medium.com/media/6aa7defa476696619664fb89c6ae81af/href

Now we can replace the assertThat() by our own, and make use of matches().

https://medium.com/media/4d74eb1dfdb0dcf78961afc727dd1354/href

Wow! That’s concise! Assertions like these are smooth to write, and straightforward to read. It really makes a difference. You can even show it to non-programmers. They’ll quickly make sense of it.

However, there’s a limitation with matches(): it ignores null values in the expected object, so we can’t use it to verify null properties. That can be an issue, particularly when we want to verify a complete object.

Verifying a complete object

To verify a complete object, including its null values, we can create an additional custom assertion: matchesExactly().

The implementation is the same as matches(), except it doesn’t call ignoringExpectedNullFields().

https://medium.com/media/ebe6f1eb5fdd387fc48e9300f2f41d5b/href

So now we can choose to verify just a few properties with matches(), or the complete object with matchesExactly().

https://medium.com/media/9a71fce813c31adffbc81b853c7eae5e/href

So, which of these 2 assertions should you use?

Probably both!

Verifying the complete object is important, but probably not in every test. You may have one test to verify the main case, there you check the complete object with matchesExactly(). And in additional tests that verify secondary edge cases, matches() can be used to check only the properties that are relevant to the tested case.

This makes the intention of each test more explicit. It also helps to structure your test suite, highlighting the main cases from the edge cases. The suite becomes easier to discover for your teammates.

With all these tips, we made tests easier to discover, easier to understand and easier to write.

Bonus part: custom Mockito matchers

Mockito is a stubbing library. We use it to verify that a service was called, and to verify what values were passed to this service.

We can follow the same logic as above, and create custom assertions to verify method arguments.

https://medium.com/media/ca9539c3cacb5f6d99312e6c76f487eb/href

For brevity, I left out the code for the custom ArgumentMatchers. You can find it on the demo repository.

That’s all, folks! Thanks for reading. You may have other tips related to unit and component testing in Java. I’d love to read your comments on the topic!

Thanks to Javarevisited for helping this post reach a much wider audience. And special thanks to my friend Julien Sobczak — some of the ideas in this article are his own, though I’ve lost track of which ones 😅.

Have a great end of the day!


I Actually Enjoy Writing Java Tests Now That I’ve Learned These Tips was originally published in Javarevisited on Medium, where people are continuing the conversation by highlighting and responding to this story.

This post first appeared on Read More