JUnit Test Method Names vs. @DisplayName Annotation
Feb 02, 2024
In software development, writing effective tests is crucial for ensuring the robustness and reliability of your code. When it comes to JUnit, a widely used testing framework in the Java ecosystem, the way you name your test methods can significantly impact the readability and maintainability of your test suite. In this article, we will explore different approaches to naming JUnit test methods, and compare them with the use of the @DisplayName
annotation, shedding light on their respective advantages and considerations.
Applying the core principle of using clear and descriptive names, I will illustrate this concept by building a practical test suite for the widely used ArrayList
in Java. The focus will be on crafting test methods that encompass essential functionalities, offering a hands-on exploration of the naming approaches discussed in this article.
Let´s focus on the following functionalities of the ArrayList
:
- When the list is created, it should be empty.
- When an item is added, it should be not empty.
- The added item is available on index 0.
Traditional Method Names: The Feature Under Test
A straightforward method of naming a test method involves simply describing the feature under test. In the context of our ArrayList
test suite, this results in method names like:
public class ArrayListTest {
@Test
public void testNewList() {}
@Test
public void testAddItem() {}
}
However, this approach has a significant drawback – the method names communicate the functionality being tested but lack information about the expected result. Furthermore, the testAddItem()
method includes multiple verification conditions, such as checking if the list is not empty and whether the added item is present at index 0.
While best practice suggests creating separate test methods for each condition, this often results in different names testAddItem1()
and testAddItem2()
. A more effective strategy involves including the expected result directly into the method name as shown in the following section.
The Given-When-Then Pattern
A popular approach for naming test methods is the given-when-then pattern to explicitly communicate the object's state under test (given), the performed functionality (when) and the expected result (then). Typically, these parts are separated by underscores, which improves the readability and structure of the test suit.
class ArrayListTest {
@Test
void aList_whenCreated_isEmpty() {}
@Test
void emptyList_itemAdded_isNotEmpty() {}
@Test
void emptyList_itemAdded_itemAvailableOnIndex0() {}
}
This approach allows for various variations, altering the order of given-when-then parts and including additional structural keywords. An example is the when-expect pattern, resulting in slightly shorter method names.
class ArrayListTest {
@Test
void when_newList_expect_isEmpty() {}
@Test
void when_addItem_expect_notEmpty() {}
@Test
void when_addItem_expect_itemAtIndex0() {}
}
For a more natural and expressive tone, longer but descriptive method names can be used, describing the state, precondition, and postcondition.
class ArrayListTest {
@Test
void whenAListIsCreatedItShouldBeEmpty() {}
@Test
void whenAddingAnItemToAnEmptyListItShouldNotBeEmpty() {}
@Test
void whenAddingAnItemToAnEmptyListTheItemShouldBeAvailableOnIndex0() {}
}
While the method names in this structured form are highly expressive, a complete natural sentence is easier to read despite these improvements.
The @DisplayName annotation
JUnit 5 introduced the @DisplayName
annotation, providing developers with a practical tool to customize display names in test methods, classes, and nested classes. This annotation's flexibility allows for the inclusion of spaces, special characters, and even emojis in test names, presenting a versatile approach to enhance test clarity and readability.
While some developers choose to combine the @DisplayName
annotation with long method names following the given-when-then pattern, I prefer compact method names to avoid unnecessary duplication and potential inconsistency.
@DisplayName("An ArrayList")
class ArrayListTest {
@Nested
@DisplayName("after instantiation")
class NewInstance {
@Test
@DisplayName("is empty")
void empty() {}
}
@Nested
@DisplayName("after adding an item")
class ItemAdded {
@Test
@DisplayName("is not empty")
void notEmpty() {}
@Test
@DisplayName("contains the item at index 0")
void itemAtIndex0() {}
}
}
In the provided example, a hierarchy is established where each test name contains its own name along with the names of its parent structures. This hierarchical structure results in clear, natural-sentence-like descriptions of test scenarios and their expected outcomes:
- An ArrayList, after instantiation, is empty
- An ArrayList, after adding an item, is not empty
- An ArrayList, after adding an item, contains the item at index 0
Additionally, this approach offers the advantage that modern IDEs can display the values of the @DisplayName
annotations in the corresponding test reports:
Conclusion
The @DisplayName
annotation in JUnit provides advantages over regular method names by offering improved readability, enhanced test reports, and structured test naming. By allowing the use of natural language and facilitating the Given-When-Then pattern, @DisplayName
creates human-readable and descriptive test names, making it easier for developers to understand the purpose and context of each test. Overall, the annotation enhances documentation within the codebase, promotes consistency across testing frameworks, and serves as a valuable tool for creating informative, well-structured, and maintainable test suites.