skip to content

@RepeatedTest

Running the same test a fixed number of times — a favorite entry point for flaky-test discussions. Interviewers check whether you can inject RepetitionInfo and customize repetition names.

on this pageshow

questions

4

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?

level: juniorimportance: should knowfreq 40%

answer

  1. @RepeatedTest(n) replaces @Test
  2. @TestTemplate → container + N child invocations
  3. @BeforeEach/@AfterEach per repetition, new instance too
  4. Default name: "repetition {currentRepetition} of {totalRepetitions}"
  5. SHORT_DISPLAY_NAME vs LONG_DISPLAY_NAME

basics

~20 s

Replace @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 s

You 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 lines
java
import 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

for a junior

Know that @RepeatedTest(n) replaces @Test, that each run is reported separately, and that the default name is "repetition X of Y".

for a middle

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.

for a senior

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.

for a principal

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.

context

open as a page

A JUnit 5 test method annotated with @RepeatedTest needs to know which iteration it is currently on — for example to seed data differently on the first run. How do you get that information, and where else can it be injected?

level: middleimportance: should knowfreq 30%

basics

~20 s

Declare a RepetitionInfo parameter on the method; JUnit's built-in resolver injects it. It exposes getCurrentRepetition(), getTotalRepetitions() and getFailureThreshold(). It can also be injected into @BeforeEach/@AfterEach — but only when that callback is running for a repeated test.

open as a page

A teammate proposes annotating a suspected intermittently-failing JUnit 5 test with @RepeatedTest(50) and leaving it in CI to catch the problem. How do you evaluate that proposal, and what would you do instead?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Repetition only catches flakes whose cause varies run to run in the same JVM and thread — random data, timing, leaked static or database state. It misses ordering, cross-test and concurrency flakes, multiplies CI time by 50, and adds no retry. Use it temporarily to reproduce and later to prove a fix; fix the root cause.

open as a page

A JUnit 5 method annotated @RepeatedTest(100) keeps executing all remaining repetitions even after the first several have failed, wasting minutes of build time. How can you make it stop early, and what are the constraints on that setting?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

Set the annotation's failureThreshold attribute, e.g. @RepeatedTest(value = 100, failureThreshold = 3). Once that many repetitions have failed, the remaining ones are automatically skipped rather than executed. It must be positive and smaller than the repetition count; it was added in JUnit 5.10.

open as a page