Good test names are one of the highest-value investments you can make in a test suite. A test named test1() tells you nothing when it fails at 2am. A test named shouldThrowIllegalArgumentExceptionWhenEmailIsNull tells you exactly what broke, why it matters, and where to look. This guide covers every JUnit 6 test naming convention and strategy β from method names and @DisplayName to display name generators and custom naming strategies.
Why Test Naming Matters
Test names serve as living documentation. They communicate:
- What is being tested (the method or feature)
- When β under what conditions
- What is expected to happen
A well-named test suite is searchable, self-documenting, and produces build reports that any developer β or product owner β can read and understand without opening the source code.
Strategy 1: The Should-When Pattern
The most widely adopted Java test naming convention. The method name reads as a sentence: "should [expected behaviour] when [condition]."
class PasswordValidatorTest {
// Pattern: should[ExpectedBehaviour]When[Condition]
@Test
void shouldReturnTrueWhenPasswordMeetsAllRequirements() { }
@Test
void shouldReturnFalseWhenPasswordIsShorterThanEightCharacters() { }
@Test
void shouldThrowIllegalArgumentExceptionWhenPasswordIsNull() { }
@Test
void shouldRequireAtLeastOneUppercaseLetter() { }
@Test
void shouldRequireAtLeastOneSpecialCharacter() { }
}
Strategy 2: The Given-When-Then Pattern (BDD Style)
Derived from Behaviour-Driven Development (BDD). Works well in teams that also write Gherkin-style specifications:
class ShoppingCartTest {
// Pattern: given[Context]_when[Action]_then[ExpectedOutcome]
// Underscores are allowed in test method names β JUnit 6 accepts them
@Test
void givenEmptyCart_whenAddingFirstItem_thenCartContainsOneItem() { }
@Test
void givenCartWithItems_whenApplyingValidCoupon_thenTotalIsReduced() { }
@Test
void givenCartWithItems_whenApplyingExpiredCoupon_thenExceptionIsThrown() { }
@Test
void givenCartAtMaxCapacity_whenAddingAnotherItem_thenCartFullExceptionIsThrown() { }
}
Strategy 3: @DisplayName β Full English Sentences
@DisplayName decouples the method name from the report display name. Use it to write complete, grammatically correct sentences that appear in IDE and HTML reports:
@DisplayName("PasswordValidator β password strength and validation rules")
class PasswordValidatorTest {
// Method name = concise identifier for developers
// @DisplayName = full readable description for everyone
@Test
@DisplayName("A password with 8+ chars, uppercase, lowercase, digit, and symbol is valid")
void strongPasswordIsValid() { }
@Test
@DisplayName("A password shorter than 8 characters is rejected")
void shortPasswordIsRejected() { }
@Test
@DisplayName("A null password throws IllegalArgumentException with descriptive message")
void nullPasswordThrowsIllegalArgumentException() { }
@Test
@DisplayName("Unicode characters in passwords are handled correctly")
void unicodePasswordIsHandledCorrectly() { }
}
IntelliJ IDEA test runner output with @DisplayName:
PasswordValidator β password strength and validation rules
β A password with 8+ chars, uppercase, lowercase, digit, and symbol is valid
β A password shorter than 8 characters is rejected
β A null password throws IllegalArgumentException with descriptive message
β Unicode characters in passwords are handled correctly
Tests run: 4, Failures: 0
Strategy 4: @DisplayNameGeneration β Automatic Name Formatting
JUnit 6 can auto-generate display names from method names using built-in generators. This avoids the overhead of writing @DisplayName on every method while still producing readable reports:
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
// ReplaceUnderscores: converts method_name_like_this to "method name like this"
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class OrderServiceTest {
// Method name: creating_order_with_valid_data_sets_status_to_pending
// Generated name: creating order with valid data sets status to pending
@Test
void creating_order_with_valid_data_sets_status_to_pending() { }
// Method name: cancelling_a_completed_order_throws_exception
// Generated name: cancelling a completed order throws exception
@Test
void cancelling_a_completed_order_throws_exception() { }
}
// IndicativeSentences: class name + method name as a sentence
@DisplayNameGeneration(DisplayNameGenerator.IndicativeSentences.class)
class InvoiceCalculatorTest {
// Generated name: "InvoiceCalculator: adding line items increases total"
@Test
void adding_line_items_increases_total() { }
}
Strategy 5: Global Name Generator via junit-platform.properties
Set the display name generator for the entire project in src/test/resources/junit-platform.properties:
# Apply ReplaceUnderscores globally to all test classes
junit.jupiter.displayname.generator.default=
org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores
Custom DisplayNameGenerator
Build your own generator by implementing DisplayNameGenerator:
import org.junit.jupiter.api.DisplayNameGenerator;
import java.lang.reflect.Method;
/**
* Custom generator: replaces camelCase with spaces and adds emoji prefix.
* Example: "creatingOrderSetsPendingStatus" becomes "β
Creating order sets pending status"
*/
public class EmojiDisplayNameGenerator implements DisplayNameGenerator {
@Override
public String generateDisplayNameForClass(Class testClass) {
// Use the simple class name with spaces added between camel-case words
return splitCamelCase(testClass.getSimpleName().replace("Test", ""));
}
@Override
public String generateDisplayNameForNestedClass(Class nestedClass) {
return splitCamelCase(nestedClass.getSimpleName());
}
@Override
public String generateDisplayNameForMethod(Class testClass, Method testMethod) {
// Add emoji prefix and split camelCase method name into words
return "β
" + splitCamelCase(testMethod.getName());
}
// Convert camelCase to space-separated words
private String splitCamelCase(String name) {
return name
.replaceAll("([A-Z])", " $1") // insert space before each uppercase letter
.toLowerCase()
.trim();
}
}
// Apply to a test class
@DisplayNameGeneration(EmojiDisplayNameGenerator.class)
class OrderServiceTest {
@Test
void creatingOrderSetsPendingStatus() { }
// Generated display name: "β
creating order sets pending status"
}
Naming Anti-Patterns to Avoid
| Anti-pattern | Example | Problem |
|---|---|---|
| Numbered tests | test1(), test2() | Zero information on failure |
| Vague verbs | testOrder(), checkUser() | No context about what is checked |
| Implementation details | testHashMapLookupPerformance() | Tests impl, not behaviour |
| Negative-only names | testInvalidEmail() | Missing: what should happen? |
| Abbreviations | tstCrtOrdSts() | Unreadable outside context |
| No context | works() | Meaningless on failure |
Frequently Asked Questions (FAQs)
Q1: Should I use @DisplayName on every test?
Not necessarily. If your method names are already descriptive (e.g., shouldThrowWhenEmailIsNull), @DisplayName adds little value and becomes maintenance overhead. Use @DisplayName when you want sentence-quality readability in reports β especially for test classes that product owners or QA engineers will review. A @DisplayNameGeneration strategy applied globally is often a better balance.
Q2: Are underscores allowed in JUnit 6 test method names?
Yes. Java allows underscores in method names, and JUnit 6 has no restriction. Combined with DisplayNameGenerator.ReplaceUnderscores, methods named given_empty_cart_when_checkout_then_throw produce clean, readable display names. This is a popular style in teams that prefer BDD-style naming.
Q3: What naming convention works best for nested test classes?
Name @Nested inner classes after the scenario or state being tested, not the method. For example: WhenCartIsEmpty, WhenUserIsLoggedIn, GivenValidPaymentMethod. This creates a hierarchical test report that reads like: "ShoppingCartTest > WhenCartIsEmpty > checkout throws EmptyCartException." See JUnit 6 Nested Tests for detailed examples.
Q4: Does @DisplayName affect how tests are filtered or selected by tag?
No. @DisplayName is purely cosmetic β it only affects what appears in reports and IDE test panels. Test selection, filtering, and tag-based execution use the underlying method name and @Tag annotations, not the display name.
Q5: Should test class names end with “Test” or “Tests”?
By convention and Maven Surefire default configuration, both *Test and *Tests suffixes are auto-discovered. The most common convention in Java projects is the singular *Test (e.g., OrderServiceTest). Use *Tests for grouping classes that cover multiple related classes (e.g., OrderServiceTests covering several related test scenarios). Consistency within your project matters more than which suffix you choose.
See Also
- JUnit 6 Tutorial: Complete Series Index
- Writing Your First Clean Test in JUnit 6 (Best Practices)
- Structuring Tests with JUnit 6 Nested Tests
- JUnit 6 Project Structure and Best Practices
- Writing Maintainable Tests in JUnit 6 (Clean Code Principles)
Conclusion
Test naming is not a cosmetic concern β it is documentation. The should-when pattern, BDD-style given-when-then naming, @DisplayName sentences, and display name generators each serve different teams and contexts. Choose one strategy, apply it consistently across your project, and your test suite will serve as a readable, searchable specification of your systemβs behaviour.
Next: Structuring Tests with JUnit 6 Nested Tests β use @Nested inner classes to group related tests and build a hierarchical, readable test plan.