I love writing tests! I get a dopamine boost every time I see those green tests in my reports. Every test failing is a tiny puzzle to solve and you get a green result as soon as you find the solution. Supergreen!
But I know that writing and reading tests can be a chore for some devs. I believe this can be improved by making the tests easier to write and turning those long and boring succession of assertEquals more meaningful and fluent to read.
Remember that a developers spends more time reading code than writing it!
With those tips, reading your tests will be a breeze and your teammates will thank you for it!
Use AssertJ instead of Junit assertions
If you use Junit assertions in your tests, chances are you are mainly using the following assertions :
assertEquals(expected, actual);
assertTrue(condition);
assertFalse(condition);
assertNull(actual);
Most of the time I see tests using assertEquals() for anything like assertEquals(true, someList.size() == 5); 😱
A common pitfall of using Junit assertEquals() and it's 2 parameters is that you can easily invert the expected value with the actual value if you are not paying attention. As long as the test is green you won't see the mistake, but if the test fail, the error message will be misleading!
Example : let's assume our method generateHash() is supposed to return a 35 chars string, but is failing :
@Test
void testHashMethod() {
String actual = underTest.generateHash("some string");
assertEquals(actual.length(), 35);
}
The error message will be misleading since the expected value is the result of the method:
org.opentest4j.AssertionFailedError:
Expected :32
Actual :35
If you are using intelliJ, you may have hints on which parameter is which :

but you won't have this hints during a code review on github for example. 🙈
Enter AssertJ
AssertJ comes with a variety of assertions that can be chained together and are specific to the type of your "actual" variable.
I've compiled some of my favorites assertions for various types. Some of those tests are redundant just to showcase the possibilities :
assertThat(actual).isEqualTo("MD55AC749FBEEC93607FC28D666BE85E73A");
assertThat(actual).hasSize(35)
.contains("A")
.startsWith("MD5")
.isUpperCase()
.isNotBlank();
assertThat(actual).hasSize(2);
assertThat(actual).contains("first value");
assertThat(actual).first().isEqualTo("first value");
assertThat(actual).containsExactly("first value", "second value");
assertThat(actual).containsExactlyInAnyOrder("second value", "first value");
assertThat(actual).containsExactlyInAnyOrderElementsOf(expected);
assertThat(actual).doesNotContain("forbidden value", "another value");
assertThat(actual).allSatisfy(s -> assertThat(s).doesNotStartWith("foo"));
assertThat(actual).isEqualTo(42);
assertThat(actual).isGreaterThan(40);
assertThat(actual).isPositive();
assertThat(actual).isIn(0, 2, 42, 1337);
assertThat(actual).isBetween(30, 50);
assertThat(actual).isAfterOrEqualTo(Instant.now())
.isBetween(pastInstant, futureInstant)
.isCloseTo(Instant.now(), within(1, ChronoUnit.MINUTES));
Throwable thrown = catchThrowable(() -> List.of(1, 2, 3).get(4));
assertThat(thrown).isInstanceOf(ArrayIndexOutOfBoundsException.class);
As you can see, the assertion always starts with assertThat(actual) which exists for any type. This as 3 advantages :
You can't invert actual and expected values
The assertion reads like a normal sentence in English, allowing you to make your test very explicit on what you intend to verify
You can easily discover which assertion is available for your type of data with your IDE auto completion! For everything else, the documentation has plenty of examples.
One last note on AssertJ : error message are often more explicit than Junit assertions. For example if your list contain one more element than expected :
java.lang.AssertionError:
Expecting:
["first value", "second value", "third value"]
to contain exactly (and in same order):
["first value", "second value"]
but some elements were not expected:
["third value"]
org.opentest4j.AssertionFailedError: more actual lines than expected: 1
If you are using Spring Boot, AssertJ is already bundled into spring-boot-starter-test along with Mockito and other testing libs. Good news!
Use the same pattern for all your tests
Define a pattern for you tests and stick to it in every test class. This makes your tests more predictable for anyone of your team reading it.
I tend to use the following pattern, suggested by Robert C. Martin in Clean Code :
@Test
void someTest() {
}
Which leads me nicely to my next point....
Use BDD syntax
Did you know that both Mockito and AssertJ offers an alternative syntax ?
For AssertJ you can use BDDAssertions.then to replace your assertThat():
import static org.assertj.core.api.BDDAssertions.then;
(...)
then(actual).isEqualTo(expected);
For Mockito you can use BDDMockito.given instead of when() :
import static org.mockito.BDDMockito.given;
(...)
when(myMock.someMethod(any())).thenReturn("Some mocked value");
given(myMock.someMethod(any())).willReturn("Some mocked value");
This will turn something like this :
@Test
void someTest() {
when(myMock.someMethod(any())).thenReturn("some string");
var expected = "MD55AC749FBEEC93607FC28D666BE85E73A";
var actual = underTest.generateHash(10);
assertThat(actual).isEqualTo(expected);
verify(myMock).someMethod(10);
}
into this :
@Test
void someTest() {
given(myMock.someMethod(any())).willReturn("some string");
var expected = "MD55AC749FBEEC93607FC28D666BE85E73A";
var actual = underTest.generateHash(10);
then(actual).isEqualTo(expected);
verify(myMock).someMethod(10);
}
Beware that BDDMockito also have a then() method, I don't recommend using both in the same class.
I found this methods fits nicely inside the "Given, When, Then" pattern with no downsides since it's a simple alias of the original methods.
Use soft assertions
If your test method contains multiples assertions but one of them fails, the test stops at the first failure but don't run the other assertions. If you have 2 assertions failing, you won't know it until you have fixed the first assertion, run the test again and get the second failure. Such a waste of time!
Some devs will argue that a test method should only have a single assertion. I prefer the concept of "one test should only test one concept". For example if the method under test returns a list I could have assertions on the size of the list, the values inside the list and their orders, test the absence of some values etc. I write all those assertions in the same test method.
Soft assertions allows you to run multiples assertions without stopping at the first failure. If anything fails you will have a report on which assertions failed.
@Test
void testList() {
var actual = List.of("first value", "second value", "third value");
var expected = List.of("first value", "second value");
var softly = new BDDSoftAssertions();
softly.then(actual).contains("first value");
softly.then(actual).first().isEqualTo("first value");
softly.then(actual).containsExactly("first value", "second value");
softly.then(actual).containsExactlyInAnyOrderElementsOf(expected);
softly.then(actual).doesNotContain("forbidden value", "another value");
softly.then(actual).allSatisfy(s -> assertThat(s).doesNotStartWith("foo"));
softly.assertAll();
}
Here is the result :
org.assertj.core.error.AssertJMultipleFailuresError:
Multiple Failures (2 failures)
-- failure 1 --
Expecting:
["first value", "second value", "third value"]
to contain exactly (and in same order):
["first value", "second value"]
but some elements were not expected:
["third value"]
at SimpleTest.testList(SimpleTest.java:49)
-- failure 2 --
Expecting:
["first value", "second value", "third value"]
to contain exactly in any order:
["first value", "second value"]
but the following elements were unexpected:
["third value"]
at SimpleTest.testList(SimpleTest.java:50)
⚠️ Warning : do not forget the line with assertAll() otherwise your test will be green but no assertions are actually run!
There are other ways to use the Soft assertions in the documentation that allows you to omit the assertAll() if you want.
Here is my favorite way:
@ExtendWith(SoftAssertionsExtension.class)
class YourTestClass {
@Test
void someTest(BDDSoftAssertions softly) {
softly.then(actual).isEqualTo(expected);
}
}
That's it for today! I still have plenty of tips regarding tests but those will be for another post.
Did you learn something new today? Do you have suggestions? Feel free to leave a comment below!