Usually, unit tests have 3 parts: Arrange, Act, and Assertion. The assertion is the test’s verification part and, It has the responsibility to answer the question: Does have this test the behavior we’re looking for?
In this tutorial, we’ll explore some alternatives to improve our test assertions to become more concise, powerful, and easy to read.
Let’s start with a simple BankAccount
class:
class BankAccount {
private final double balance;
// Constructor, getter, equals & hashcode
}
Now, lets create a list of bank accounts:
//Arrange
var accounts = List
.of(new BankAccount(100), new BankAccount(200),
new BankAccount(300), new BankAccount(400),
new BankAccount(500));
and filter it by a fixed amount balance, let’s say 300:
//Act
int limit = 300;
var result = accounts
.stream()
.filter(account -> account.getBalance() >= limit)
.collect(Collectors.toList());
According to limit
, when we filter the list, we’ll get in result
a new list with a size of 3.
At this point, we already accomplished the arrange, and the act parts, in the next sections we explore how to do the assert part.
assertEquals
An alternative to verify it is with the assertEquals
method:
assertEquals(result.size(), 3);
Don’t get me wrong, assertEquals
works great but lacks expressiveness, and we need to think carefully in which order we need to put the params, for example, what is the difference between:
assertEquals(result.size(), 3);
and:
assertEquals(3, result.size());
We can think is the same, actually, when the test is OK we’ll get a green result.
Now, lets make the test fail changing the number 3 to 5.
assertEquals(result.size(), 5);
in the first case we’ll get:
org.opentest4j.AssertionFailedError:
Expected :3
Actual :5
and in the second case:
assertEquals(5, result.size());
we’ll see:
org.opentest4j.AssertionFailedError:
Expected :5
Actual :3
With these outputs I can’t guess which is the problem; is it the expected value? or is it the calculation result?.
When we work in a test suite with multiple unit tests it is very important to maintain the expressiveness to fix bugs and improve readability.
assertThat
insteadAn alternative option to assertEquals
is assertThat
. JUnit offers us the possibility of using a variety of third-party assertion libraries to enrich our matchers options.
An example of these matchers libraries is Hamcrest. With Hamcrest we can rewrite our assertion part to something like this:
assertThat(result, hasSize(3));
A key difference is expressiveness. Now, let’s see what happen if change the size expected to 5:
assertThat(result, hasSize(5));
We’ll get a more descriptive error:
java.lang.AssertionError:
Expected: a collection with size <5>
but: collection size was <3>
The last Hamcrest version can be found in Maven Central.
Another powerful assertion library is AssertJ, actually, top frameworks like Spring, Hibernate, and JUnit 5 are using it.
With the AssertJ fluent syntax, we can check the size of the list and add extra verifications; for example that the BackAccount
with 100 balance isn’t present:
assertThat(result)
.hasSize(3)
.doesNotContain(new BankAccount(100));
and the error message (if we change the size expected to 5) is very helpful:
java.lang.AssertionError:
Expected size:<5> but was:<3> in:
<[BankAccount{balance=300.0},
BankAccount{balance=400.0},
BankAccount{balance=500.0}]>
The last AssertJ’s version can be found in Maven Central.
In this tutorial, we saw how to use different assertions libraries to improve our unit tests to become more expressive, easy to read and maintain.
The full code can be found over on Github.