Use @DisplayName
and @Nested
to Document and Structure JUnit Tests
Keep test code better maintainable by documenting and structuring tests with JUnit-provided annotations.
Keeping code maintainable is one of the biggest challenges in software engineering. This is true not only for production
code but also for tests. Apart from basics like focussing on one single aspect and not trying to test everything in one
test case, it is advisable to structure and document tests in a way, that developers have a chance to understand painlessly
the intention and are able to adjust tests as needed when e.g. the code under test has changed. One way of achieving this is by using the JUnit-provided annotations @DisplayName
(official docs) and @Nested
(official docs).
The subject under test for the following examples is a simple list holding attachments:
1public class AttachmentList extends ArrayList<Attachment> {
2 public boolean add(final Attachment attachment) {
3 Objects.requireNonNull(attachment);
4
5 if (attachment.getSize() <= 0) {
6 throw new IllegalArgumentException("Only attachments with sizes > 0 allowed");
7 }
8
9 return super.add(attachment);
10 }
11}
By default, all tests in a test class are structured in a flat way and e.g. Intellij simply prints the method names for each
test case and the result when executing a test suite. Many developers try to express the intention of a test through the
test method name and are used to the times when it was mandatory to prefix all test cases with test
which leads
to method names like test
or even
worse test_
. This is problematic in several ways:
- Long method names should be avoided when possible, it makes the code harder to read; as always in software engineering: Keep it simple.
- Proper sentences with real grammar and punctuation are easier to read and understand; we are humans, not machines.
- Underscores in method names are against the Java Code Conventions and a No-Go.
With the legacy approach (omitting the underscores), the test execution in Intellij looks like this:
The intention of the individual test cases is more or less clear, but it is not easy to read. The better approach is to keep the test method name short, simple and tidy and to use @DisplayName
to express and document the intention of a test case like
1@Test
2@DisplayName("should throw a NPE when trying to add null")
3void nullObject() {
4 assertThrows(NullPointerException.class, () -> attachmentList.add(null));
5}
Simply by using @DisplayName
annotations on all test methods and the test class itself the text execution looks much cleaner:
When a test case fails, it is clear at a glance what was tested and failed.
Still, there is no inner structure, which isn't a problem in this small example, but often test classes contain many tests targeting different aspects of the subject under test.
To get the set of test cases structured in a better way, inner classes annotated with the @Nested
annotation can be used which allows grouping tests together
1@DisplayName("The AttachmentList")
2class AttachmentListTest {
3 private AttachmentList attachmentList;
4
5 @BeforeEach
6 void setUp() {
7 attachmentList = new AttachmentList();
8 }
9
10 @Nested
11 @DisplayName("when adding attachments")
12 class WhenAdding {
13 @Test
14 @DisplayName("should throw a NPE when trying to add null")
15 void nullObject() {
16 assertThrows(NullPointerException.class, () -> attachmentList.add(null));
17 }
18 }
19}
and of course the inner classes can also be annotated with @DisplayName
. Utilizing this we can form proper sentences which makes reading and understanding tests and its executions a real pleasure:
Adding these annotations causes only very little effort but helps all developers to understand much better what's going on in the code - especially after some time when the memory is not so fresh anymore or the developers who created the code initially have long left.
The full example project with all classes can be found on my GitLab instance.
This post is part of Best Practices, a series of short posts which summarize dos and don'ts I find useful in my daily work. Some of them are common sense, others might be a question of taste, some might even be controversial. All are my personal opinion. You see things differently? Leave me a comment on Mastodon or by E-Mail.