Existing test suites accumulate technical debt just like production code. Tests that were written quickly become brittle, slow, hard to read, or duplicated. Optimising and updating your JUnit 6 tests is not just housekeeping — it directly determines whether your test suite remains a trusted safety net or becomes a maintenance burden that developers work around.
This post gives you 10 targeted AI prompts designed to refactor, improve, and modernise existing JUnit 6 test code. Each prompt addresses a specific quality problem — from brittle mocking to slow Spring contexts to missing boundary cases — and produces immediately usable improved test code.
For foundational context, see Writing Maintainable Tests in JUnit 6 and Refactoring Legacy Tests to JUnit 6.
Prompt 1: Improve Readability and Add Missing @DisplayName
When to use: Your existing tests have cryptic method names like test1(), testMethod(), or generic names that give no information when they fail in CI.
Refactor the following JUnit 6 test class to improve readability and self-documentation.
Changes required:
1. Add @DisplayName("full English sentence describing the behaviour") to every @Test method
2. Add @DisplayName to the test class itself describing what is being tested
3. Rename test methods to follow the "shouldExpectedBehaviourWhenCondition" pattern
4. Add blank lines between Arrange, Act, and Assert phases in every test
5. Add a comment // Arrange, // Act, // Assert at the start of each phase ONLY if
the test is long enough to need navigation guidance (10+ lines)
6. Replace any magic numbers with named constants with meaningful names
7. Add assertion messages to every assertEquals/assertTrue/assertFalse call
8. Do NOT change any test logic — only improve naming and structure
Test class to refactor:
[PASTE YOUR EXISTING TEST CLASS HERE]
Prompt 2: Convert JUnit 4 Tests to JUnit 6
When to use: You have legacy JUnit 4 test classes that need migrating to JUnit 6 as part of a broader upgrade or codebase modernisation.
Migrate the following JUnit 4 test class to JUnit 6 (JUnit Jupiter).
Migration rules to apply:
- Replace: org.junit.Test → org.junit.jupiter.api.Test
- Replace: @Before → @BeforeEach, @After → @AfterEach
- Replace: @BeforeClass → @BeforeAll, @AfterClass → @AfterAll
- Replace: @Ignore → @Disabled (add a reason string if one was present)
- Replace: @Test(expected = X.class) → assertThrows(X.class, () -> ...)
- Replace: @Test(timeout = n) → @Timeout(value = n, unit = TimeUnit.MILLISECONDS)
- Replace: @RunWith(MockitoJUnitRunner.class) → @ExtendWith(MockitoExtension.class)
- Replace: Assert.assertEquals → assertEquals (with static import from Assertions)
- Remove all public modifiers from test class and test methods
- Add @DisplayName with a descriptive sentence to every test method
- Convert @Parameterized tests to @ParameterizedTest with @CsvSource or @MethodSource
Additional improvements while migrating:
- Replace @Test(expected=) with assertThrows() AND verify the exception message
- Replace Assert.assertThat(hamcrest) with AssertJ assertThat() where possible
JUnit 4 test class to migrate:
[PASTE YOUR JUNIT 4 TEST CLASS HERE]
Prompt 3: Refactor Slow Tests to Eliminate Unnecessary Spring Contexts
When to use: Your unit tests are annotated with @SpringBootTest but do not actually need the Spring context — they are testing pure Java logic and could run in milliseconds instead of seconds.
Refactor the following test class to remove the unnecessary Spring context and make it
a true JUnit 6 unit test.
Analysis to perform:
1. Identify which @Autowired beans are used in the tests
2. For beans that are plain Java classes (no I/O, no DB, no HTTP), replace
@Autowired with direct instantiation: new ClassName(dependencies)
3. For beans that call external services, replace with @Mock using Mockito
4. Replace @SpringBootTest with @ExtendWith(MockitoExtension.class)
5. Replace @Autowired with @InjectMocks for the class under test
6. Keep @SpringBootTest ONLY if the test genuinely requires Spring wiring
(e.g., tests @Transactional behaviour, tests actual DB queries)
Expected outcome:
- Startup time: from ~5 seconds to <50 milliseconds
- No loss of test coverage
Test class to refactor:
[PASTE YOUR SLOW @SpringBootTest UNIT TEST CLASS HERE]
Service classes (to determine if they have Spring dependencies):
[PASTE THE SERVICE CLASSES BEING TESTED HERE]
Prompt 4: Replace Brittle Mock Verification with Behaviour-Focused Tests
When to use: Your tests verify too many internal method calls — they break every time you refactor the implementation, even when the behaviour is unchanged.
Refactor the following tests to reduce brittle mock over-specification.
Analysis to perform:
1. Identify any verify() calls that test internal implementation details rather than
externally observable behaviour (e.g., verifying cache invalidation, audit logging,
metrics counters, internal helper method calls)
2. Remove verify() calls for internal concerns — keep only those that verify the
SERVICE CONTRACT (e.g., sending an email, persisting to the DB, calling an external API)
3. Identify stubs that are declared but never actually needed for the test assertion
— remove them (they generate UnnecessaryStubbingException)
4. For remaining verify() calls, make them more specific using ArgumentCaptor to also
verify WHAT was passed, not just THAT the method was called
5. Focus assertions on the RETURN VALUE and STATE of the object under test
Definition of "service contract" for this class:
[DESCRIBE WHAT THIS SERVICE'S EXTERNAL CONTRACTS ARE, e.g. "sends email on order confirmation, persists order to DB, returns confirmed order DTO"]
Test class to refactor:
[PASTE YOUR OVER-SPECIFIED TEST CLASS HERE]
Prompt 5: Add Missing Boundary and Edge Case Tests
When to use: Your test class has good happy-path coverage but mutation testing (PIT) shows surviving mutants because boundary conditions are not tested.
Analyse the following test class and add missing boundary value and edge case tests
that would kill surviving PIT mutation testing mutants.
Boundary categories to cover for every numeric parameter:
- Exactly at the threshold (e.g., if condition is > 100, test with exactly 100 and 101)
- One below the threshold
- One above the threshold
- Zero
- Negative values (if applicable)
- Maximum integer / Long.MAX_VALUE (if applicable)
Boundary categories for String parameters:
- null
- empty string ""
- whitespace only " "
- single character
- exactly at max length
- one character over max length
- Unicode and special characters
For each added test:
- Name it "shouldBehaviour_whenExactBoundaryCondition"
- Add @DisplayName explaining WHY this boundary is significant
- Include a comment citing the business rule that defines the boundary
Existing test class:
[PASTE YOUR EXISTING TEST CLASS HERE]
Production class (for context on boundary logic):
[PASTE THE PRODUCTION CLASS HERE]
Known surviving PIT mutants (if available):
[PASTE PIT REPORT LINES FOR SURVIVING MUTANTS HERE]
Prompt 6: Consolidate Duplicate Tests into @ParameterizedTest
When to use: You have 5–10 nearly identical test methods that differ only in their input values — a code smell that can be replaced with a single clean parameterized test.
Refactor the following test class by consolidating duplicate test methods into
@ParameterizedTest methods.
Identify groups of tests that:
- Test the same method with different inputs
- Have identical Arrange-Act-Assert structure except for the data values
- Could be expressed as a row in a data table
For each identified group:
1. Create a single @ParameterizedTest method with @CsvSource (inline data)
or @MethodSource (for complex objects)
2. The name= attribute should show the input values in the test report
3. Keep the original separate tests ONLY if they test genuinely different
behaviour (not just different data)
4. The parameterized version must have at least as many cases as the original
individual tests (do not reduce coverage while consolidating)
Also identify:
- Tests that differ only in the expected exception type → use @ParameterizedTest
with a Stream of Arguments containing (input, expectedExceptionClass)
- Tests that verify valid vs invalid variations → use @NullAndEmptySource + @ValueSource
Test class to consolidate:
[PASTE YOUR TEST CLASS WITH DUPLICATE TEST METHODS HERE]
Prompt 7: Add assertAll for Multi-Property Object Verification
When to use: Your tests have long chains of individual assertions on the same object. When one fails, the rest are skipped and you do not see the full picture of what is broken.
Refactor the following tests to use assertAll() for multi-property object verification.
Rules:
1. Find any test that has 2 or more sequential assertions on the SAME object
(e.g., 3 assertEquals calls on fields of the same 'order' object)
2. Wrap them in assertAll("descriptive group name", () -> ..., () -> ..., ...)
3. The group name should describe the object being verified:
e.g., assertAll("created order initial state", ...)
4. Each lambda inside assertAll should assert ONE property with a clear message:
() -> assertEquals(expectedId, order.getId(), "Order ID")
5. Keep assertions that test DIFFERENT objects as separate assertAll blocks
or separate assertions (do not mix unrelated objects in one assertAll)
6. Assertions that are preconditions (e.g., assertNotNull before accessing a field)
should remain OUTSIDE assertAll so they fail fast if the object is null
Test class to refactor:
[PASTE YOUR TEST CLASS HERE]
Prompt 8: Restructure Flat Test Class into @Nested Groups
When to use: A test class has grown to 30+ test methods in a flat structure and is now hard to navigate. You want scenario-based grouping that produces a hierarchical, readable test report.
Restructure the following flat JUnit 6 test class into @Nested inner classes.
Grouping strategy:
1. Group tests by the SCENARIO or STATE they test, not by method name
Example groups: "WhenCartIsEmpty", "WhenCartHasItems", "WhenDiscountIsApplied"
2. Each @Nested class should have @DisplayName that reads as a conditional phrase
e.g., @DisplayName("When the cart contains items")
3. Move the relevant @BeforeEach setup INTO the @Nested class where possible
so each nested class has the minimal, specific setup it needs
4. Outer class @BeforeEach should only contain setup shared by ALL nested classes
5. Test methods inside nested classes should have SHORT @DisplayName
(the full context comes from the class name, so "returns correct total" is enough)
6. The resulting test report should read:
CartService > When the cart is empty > checkout throws EmptyCartException
CartService > When the cart has items > total equals sum of item prices
Produce:
- The complete refactored test class with all @Nested groups
- A proposed test report tree showing what the output will look like
Flat test class to restructure:
[PASTE YOUR FLAT TEST CLASS HERE]
Prompt 9: Update Tests After Production Code Refactoring
When to use: You have refactored the production code (renamed methods, changed signatures, extracted services) and need to update the corresponding tests to compile and stay green without changing what they verify.
Update the following JUnit 6 test class to work with the refactored production code.
What changed in production code:
[DESCRIBE THE CHANGES, e.g.:
- Method 'calculateDiscount(double price)' renamed to 'applyDiscount(double basePrice, DiscountType type)'
- OrderService split into OrderCreationService and OrderFulfilmentService
- Constructor changed from new OrderService(repo) to new OrderService(repo, emailService, config)
- Return type changed from Order to OrderResult
- Exception type changed from IllegalArgumentException to OrderValidationException]
Rules for updating tests:
1. Update all method calls to match the new signatures
2. Update all @InjectMocks / new Constructor() calls to match new constructors
3. Update import statements for renamed/moved classes
4. Update assertThrows() to the new exception type
5. Update field access assertions to match new return type fields
6. Do NOT change WHAT is being tested — only HOW the test calls the production code
7. Flag any tests that are now REDUNDANT (testing behaviour that no longer exists)
8. Flag any tests that need NEW cases because the new code has new branches
Old test class:
[PASTE THE FAILING TEST CLASS HERE]
New production class (after refactoring):
[PASTE THE REFACTORED PRODUCTION CLASS HERE]
Prompt 10: Full Test Class Audit and Quality Improvement
When to use: You want a comprehensive AI review of an existing test class that identifies all quality issues and produces an improved version in one pass — useful for code review preparation or legacy codebase clean-up.
Perform a full quality audit of the following JUnit 6 test class and produce an improved version.
Audit checklist to apply:
[ ] Are there test methods with no assertions? → Add meaningful assertions
[ ] Are assertEquals arguments in wrong order (actual, expected instead of expected, actual)?
→ Swap to (expected, actual, "message")
[ ] Do any tests have assertions without messages? → Add descriptive messages
[ ] Are there @SpringBootTest tests that could be plain unit tests? → Convert them
[ ] Are there duplicate test methods testing the same thing with different data?
→ Consolidate into @ParameterizedTest
[ ] Are there brittle verify() calls on internal implementation details? → Remove them
[ ] Are there Thread.sleep() calls? → Replace with Awaitility
[ ] Are there hardcoded test data values with no explanation? → Replace with named constants
[ ] Do test method names describe behaviour or just method names? → Rename + add @DisplayName
[ ] Are there tests that depend on execution order (shared mutable static state)? → Fix isolation
[ ] Does @BeforeEach do too much unrelated setup? → Split into focused setup per @Nested group
[ ] Are all exception tests using assertThrows with message verification? → Update legacy patterns
Output format:
1. Audit findings: a numbered list of every issue found with its line number
2. Severity: HIGH (test gives false confidence) / MEDIUM (readability) / LOW (style)
3. Complete improved test class with all issues fixed
4. Summary: lines changed, tests added, tests removed
Test class to audit:
[PASTE YOUR TEST CLASS HERE]
Workflow: Applying These Prompts Systematically
For a legacy test suite, apply these prompts in this order for maximum impact:
- Prompt 2 first if migrating from JUnit 4 — get everything onto the same platform
- Prompt 3 next — eliminate slow @SpringBootTest unit tests for the biggest speed win
- Prompt 10 for a full audit — understand the scope of quality issues
- Prompt 6 to consolidate duplicate tests — reduces maintenance overhead
- Prompt 8 to restructure with @Nested — improves navigability
- Prompt 1 for readability — makes the improved suite self-documenting
- Prompt 5 to add boundary tests — drives mutation score up
Frequently Asked Questions (FAQs)
Q1: How do I know which tests in my suite most need improving?
Run mutation testing with PIT first — see Mutation Testing with PIT and JUnit 6. The surviving mutants report tells you exactly which tests are failing to catch bugs. Focus your optimisation effort on the classes with the lowest mutation scores. Supplement this with a code review pass using the checklist in Prompt 10, which catches structural issues PIT does not flag.
Q2: Is it safe to let AI refactor test code without human review?
No. Always review AI-refactored test code before committing. The primary risk is that AI may change what is being tested (not just how it is expressed) when renaming methods, consolidating parameterized tests, or restructuring nested classes. The safest workflow: apply the prompt, review the diff carefully against the original, run the test suite to confirm all tests still pass and the mutation score has not dropped.
Q3: After refactoring with Prompt 8 (@Nested), do I need to update CI tag filters?
Only if you added or changed @Tag annotations during the restructure. Nested classes inherit tags from their enclosing class, so @Tag("unit") on the outer class applies to all nested tests. If you add a new @Tag("slow") to a specific nested class, update your Maven/Gradle tag filter configuration accordingly. See Tags and Test Suites in JUnit 6.
Q4: Can Prompt 9 handle cases where the production class was deleted entirely?
Yes, but you need to modify it: add “the following production class was deleted and the tests for it should be removed. Identify which test methods no longer have a production counterpart and list them for deletion.” Provide the old class and explain what replaced its functionality. The AI will map the old tests to their new equivalents and flag tests that are now obsolete.
Q5: How often should I run these optimisation prompts on a living codebase?
Build quality checks into your code review process rather than running occasional batch optimisations. Add a lightweight version of Prompt 10’s checklist to your PR review template. Run PIT on a weekly schedule to catch mutation score regressions early. Schedule full test suite audits (Prompt 10) quarterly or when onboarding a new developer who will work heavily in the test suite.
See Also
- Writing Maintainable Tests in JUnit 6 (Clean Code Principles)
- Refactoring Legacy Tests to JUnit 6 (Migration Playbook)
- Common JUnit Testing Mistakes and How to Avoid Them
- Mutation Testing with PIT and JUnit 6
- JUnit 6 Tutorial: Complete Series Index
Conclusion
Existing test suites rarely stay clean without deliberate effort. These 10 prompts give you an AI-powered toolkit for every common test quality problem: poor naming, slow contexts, brittle mocking, duplicate tests, flat structure, missing boundaries, legacy JUnit 4 patterns, and broken-after-refactoring tests. Apply them systematically — starting with the issues that have the highest impact on speed and correctness — and your test suite will become progressively more trustworthy and maintainable with each iteration.