In JUnit 5 (Jupiter), how do you make a single test method execute several times in a row, and how do you keep each execution distinguishable in the test report?
answer
- @RepeatedTest(n) replaces @Test
- @TestTemplate → container + N child invocations
- @BeforeEach/@AfterEach per repetition, new instance too
- Default name: "repetition {currentRepetition} of {totalRepetitions}"
- SHORT_DISPLAY_NAME vs LONG_DISPLAY_NAME
basics
~20 sReplace @Test with @RepeatedTest(n). Jupiter runs the method n times as n separate tests, each with its own @BeforeEach/@AfterEach. Default names read "repetition 1 of 10"; customise them with the annotation's name attribute using {currentRepetition} and {totalRepetitions}.
solid answer
~50 sYou swap `@Test` for `@RepeatedTest(10)`. It is a `@TestTemplate`, so the method becomes a **container** whose children are ten individual invocations — each one is reported separately, and the full per-test lifecycle (`@BeforeEach`, `@AfterEach`, and with the default per-method instance lifecycle a **fresh test-class instance**) runs for every repetition. A failure in repetition 3 does not stop repetitions 4-10 unless you set `failureThreshold`. The default display name is `"repetition {currentRepetition} of {totalRepetitions}"` (the constant `RepeatedTest.SHORT_DISPLAY_NAME`). You override it with the `name` attribute and the placeholders `{displayName}`, `{currentRepetition}`, `{totalRepetitions}` — or use the built-in `RepeatedTest.LONG_DISPLAY_NAME`, which prefixes the method's own display name. ```java @RepeatedTest(value = 5, name = "{displayName} — run {currentRepetition}/{totalRepetitions}") @DisplayName("token generator produces unique ids") void generatesUniqueIds() { ... } ``` Most suites need it rarely: for randomised input, warm-up-sensitive code, or to demonstrate that a flake fix holds.
code
java · 19 linesimport org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import static org.junit.jupiter.api.Assertions.assertTrue;
class TokenGeneratorTest {
private final TokenGenerator generator = new TokenGenerator();
@DisplayName("token generator produces non-blank ids")
@RepeatedTest(value = 5, name = "{displayName} - run {currentRepetition}/{totalRepetitions}")
void producesNonBlankIds() {
assertTrue(generator.next().length() > 0);
}
@RepeatedTest(value = 3, name = RepeatedTest.LONG_DISPLAY_NAME)
void usesTheBuiltInLongTemplate() {
assertTrue(generator.next().startsWith("tok_"));
}
}go deeper
Know that @RepeatedTest(n) replaces @Test, that each run is reported separately, and that the default name is "repetition X of Y".
Add the lifecycle detail — per-repetition @BeforeEach/@AfterEach and a fresh instance — and the name template placeholders, and contrast it with a hand-written loop.
Talk about what does and does not reset between repetitions (instance state resets, static/DB/singleton state does not) and the cost of multiplying an expensive setup by the repetition count.
Frame when repetition belongs in the permanent suite at all versus being a temporary diagnostic, and the reporting/CI-time consequences of N-fold test node explosion.
## What `@RepeatedTest` is `@RepeatedTest` is a JUnit Jupiter annotation that replaces `@Test` on a method and tells the engine to execute that method a fixed number of times. `@RepeatedTest(10)` runs the method ten times. Mechanically it is not a special case of `@Test`: `@RepeatedTest` is meta-annotated with `@TestTemplate`. A test template is a method that produces *many* invocations, each of which the engine reports as its own test. That is why, in an IDE or an XML report, a repeated test appears as a **container node** (named after the method) with N **child nodes** underneath it, rather than as one test that happened to loop internally. The distinction matters. If you wrote the loop yourself — ```java @Test void generatesUniqueIds() { for (int i = 0; i < 10; i++) { assertNotNull(generator.next()); } } ``` — you would get **one** test result, the first failure would abort the remaining iterations, and setup would run once for all ten iterations. `@RepeatedTest` gives you ten results, ten independent setups, and (by default) execution of every repetition regardless of earlier failures. ## Lifecycle per repetition Each repetition is a full test execution: - `@BeforeEach` and `@AfterEach` run **once per repetition**. - With the default `Lifecycle.PER_METHOD` test-instance lifecycle, a **new instance of the test class** is constructed for each repetition, so instance fields are reset every time. - `@BeforeAll` / `@AfterAll` still run once for the whole class, not per repetition. - Extensions that hook per-test callbacks fire per repetition. The practical consequence: instance state does not leak between repetitions, but anything *outside* the instance does — static fields, singletons, a shared database, an embedded server, files on disk. If a repeated test passes on repetition 1 and fails on repetition 2, the usual culprit is exactly that kind of shared state (for example, an insert that violates a unique constraint the second time round). ## Display names and the name template Because each repetition is its own reported test, it needs its own name. The annotation's `name` attribute is a template with three placeholders: - `{displayName}` — the display name of the method itself (from `@DisplayName`, or the generated name). - `{currentRepetition}` — the 1-based index of this repetition. - `{totalRepetitions}` — the configured total. Two constants ship with the annotation: - `RepeatedTest.SHORT_DISPLAY_NAME` = `"repetition {currentRepetition} of {totalRepetitions}"` — this is the **default**. - `RepeatedTest.LONG_DISPLAY_NAME` = `"{displayName} :: repetition {currentRepetition} of {totalRepetitions}"`. So out of the box you see `repetition 3 of 10`, and the enclosing container carries the method's display name. Setting `name = RepeatedTest.LONG_DISPLAY_NAME` repeats the method name on every child line, which is useful when your CI report flattens the tree and the container name is lost. The `value` attribute (the repetition count) must be positive; `@RepeatedTest(0)` or a negative count is a configuration error, not a silently skipped test. ## When it earns its place Legitimate uses: - **Randomised or property-ish input** — the method draws a fresh random value each run and you want many samples reported individually. - **Warm-up-sensitive behaviour** — caches, lazy initialisation, connection pools that behave differently on the first call than on the hundredth. - **Proving a fix** — you temporarily crank the count while you demonstrate that a previously intermittent test is now stable, then dial it back. - **Idempotency checks** — running the same operation repeatedly must converge to the same state. It is a weak permanent tool for hunting flaky tests, because every repetition runs in the same JVM, on the same thread, in the same order — which is precisely the configuration that just passed. ## Common mistakes - Leaving `@Test` *and* `@RepeatedTest` on the same method. `@Test` is not a repeatable/composable partner here; the method should carry only `@RepeatedTest`. - Assuming setup runs once. It runs per repetition, so an expensive `@BeforeEach` multiplied by 200 repetitions is a slow suite. - Assuming a failure stops the rest. By default it does not — every repetition still runs (see `failureThreshold` if you want early exit). - Depending on order: repetition 5 must not rely on state left by repetition 4. If it does, you have written a stateful sequence, not a repeated test.
- If repetition 2 of a @RepeatedTest fails, what happens to repetitions 3 through 10?By default they all still run. Each repetition is an independent test invocation, so a failure is recorded against that repetition only and execution continues. That is deliberate — you usually want to know whether it fails 1 time in 10 or 9 times in 10. If you want the engine to stop early, set the annotation's failureThreshold attribute, which skips the remaining repetitions once the given number of failures has been recorded.
- How is @RepeatedTest(10) different from writing a for-loop with ten assertions inside a single @Test?The loop produces one test result, one setup/teardown pair, and stops at the first failed assertion, so you learn nothing about iterations after the failure. @RepeatedTest produces ten independently reported results, runs @BeforeEach/@AfterEach — and constructs a fresh test-class instance — for each one, and keeps going after a failure. That makes both the failure rate and the isolation between iterations visible.
A hand-rolled for-loop inside one @Test is a single sprint you either finish or don't; @RepeatedTest is ten separate heats, each with its own warm-up and its own line on the scoreboard.
saying these in an interview costs you the question
- Saying @BeforeEach runs only once for the whole repeated test.
- Believing the first failing repetition aborts the rest by default.
- Putting both @Test and @RepeatedTest on the same method.
- Assuming repetitions share the test-class instance, so instance fields accumulate across runs (they do not, under the default per-method lifecycle).
- Thinking the ten repetitions are reported as a single test result rather than ten.