skip to content

Which JUnit 4 features (e.g. @Rule, @RunWith runners, @Category) keep working under the Vintage engine, and which JUnit 5 features can JUnit 4 tests NOT use just by being run via Vintage?

level: seniorimportance: should knowfreq 35%

answer

  1. Vintage = faithful JUnit 4 runtime
  2. @Rule, @RunWith, @Category all keep working
  3. Categories map to Platform tags
  4. No Jupiter features without rewrite
  5. Bridge, not a migration tool

basics

~20 s

Vintage faithfully runs JUnit 4 tests as JUnit 4: @Rule, @RunWith runners (Parameterized, Suite, SpringRunner), @Category, @Ignore all work. But Vintage doesn't grant them Jupiter features like @ParameterizedTest, extensions, or @Nested — those need a real rewrite.

solid answer

~40 s

The Vintage engine is a faithful JUnit 4 runtime on top of the Platform, so JUnit 4 mechanics keep working: `@Rule`/`@ClassRule`, custom `@RunWith` runners (`Parameterized`, `Suite`, `SpringJUnit4ClassRunner`/`SpringRunner`, `MockitoJUnitRunner`), `@Before`/`@After`, `@BeforeClass`, `@Ignore`, expected-exception `@Test(expected=...)`, and timeouts. Filtering by `@Category` works through the Platform's tag mechanism — Vintage maps JUnit 4 categories to Platform **tags**, so `useJUnitPlatform { includeCategories(...) }` / `excludeCategories(...)` selects them. What Vintage does **not** do is *upgrade* those tests: a JUnit 4 test running under Vintage cannot use Jupiter's `@ParameterizedTest`, the `Extension` API, `@Nested`, `@TestFactory`, `Assertions.assertAll`, or `@DisplayName`. Those are Jupiter constructs; using them requires migrating the test to the `org.junit.jupiter.api` model. So Vintage = run unchanged, not modernize.

code

kotlin · 6 lines
kotlin
tasks.named<Test>("test") {
    useJUnitPlatform {
        includeCategories("com.example.Slow")  // JUnit 4 @Category(Slow.class)
        includeTags("integration")              // JUnit 5 @Tag("integration")
    }
}

go deeper

for a junior

Know that ordinary JUnit 4 tests (@Test, @Before, @Rule) just keep working under Vintage.

for a middle

List supported features (@RunWith runners, @Category) and that categories filter via the platform config.

for a senior

Articulate the boundary: Vintage runs but does not modernize; name the Jupiter equivalents (SpringExtension, MockitoExtension) for the rewrite.

for a principal

Decide which legacy runners (Spring, Mockito) gate migration ordering and codify category→tag conventions across the org.

## Vintage runs JUnit 4 *as JUnit 4* The `VintageTestEngine` wraps JUnit 4's own runner infrastructure (`org.junit.runner.Runner`, `RunNotifier`) and adapts its results to the Platform's `TestDescriptor`/listener model. Because it delegates to real JUnit 4 internals, virtually everything JUnit 4 offers continues to work: ### Works under Vintage - **`@Rule` / `@ClassRule`** — `TemporaryFolder`, `ExpectedException`, `Timeout`, custom `TestRule`/`MethodRule`. - **Custom `@RunWith` runners** — `Parameterized`, `Suite`, `Theories`, `SpringJUnit4ClassRunner`/`SpringRunner`, `MockitoJUnitRunner`, `Enclosed`. Vintage simply asks the declared runner to run the class. - **Lifecycle** — `@BeforeClass`, `@AfterClass`, `@Before`, `@After`. - **`@Ignore`**, **`@Test(expected = X.class)`**, **`@Test(timeout = n)`**, **`Assume.*`**. - **`@Category`** filtering — see below. ### Category → tag mapping JUnit 4's `@Category(Slow.class)` is mapped by Vintage onto Platform **tags**. In Gradle you filter via the platform config: ```kotlin tasks.named<Test>("test") { useJUnitPlatform { includeCategories("com.example.Slow") // run only @Category(Slow.class) // or excludeCategories("com.example.Slow") } } ``` For Jupiter tests in the same task you'd use `includeTags`/`excludeTags`; categories and tags share the Platform's selection machinery. ## What Vintage does NOT give you Vintage executes; it does not transform. A JUnit 4 test cannot suddenly use Jupiter-only features merely because Vintage runs it: - **`@ParameterizedTest` / `@ValueSource` / `@MethodSource`** — Jupiter only; JUnit 4 must use `Parameterized` runner instead. - **JUnit 5 `Extension` API** (`@ExtendWith`, `BeforeEachCallback`, `ParameterResolver`) — Jupiter only; JUnit 4 uses `@Rule`/runners. - **`@Nested`, `@TestFactory`, `@RepeatedTest`, `@DisplayName`, `Assertions.assertAll`/`assertThrows`** — all `org.junit.jupiter.api`. To use any of these the test must be rewritten in the Jupiter programming model. This is the crux: **Vintage is a compatibility bridge, not a migration tool.** ## Practical implications - Spring's `@RunWith(SpringRunner.class)` tests keep passing under Vintage; the modern equivalent is `@ExtendWith(SpringExtension.class)` (or `@SpringBootTest`, which bundles it) once you move to Jupiter. - Mockito's `@RunWith(MockitoJUnitRunner.class)` works; the Jupiter equivalent is `@ExtendWith(MockitoExtension.class)`. - A mixed module commonly filters JUnit 4 by category and JUnit 5 by tag in the same `useJUnitPlatform { }` block. ## Edge: rule-to-extension shims JUnit 5 offers `@EnableRuleMigrationSupport` (in `junit-jupiter-migrationsupport`) so a *Jupiter* test can reuse certain JUnit 4 `TestRule`s. That is the opposite direction — it's about reusing rules in Jupiter, not about Vintage, and is worth knowing for migration but not part of running tests under Vintage.

  • A teammate adds @ParameterizedTest to a class annotated with org.junit.Test and it doesn't run. Why?
    Mixing JUnit 4 and Jupiter annotations in one class is unsupported. @ParameterizedTest is Jupiter; the class is claimed by Vintage as a JUnit 4 test, which ignores the Jupiter annotation. The class must be fully migrated to the Jupiter model.
  • How do you filter JUnit 4 @Category tests from Gradle?
    Vintage maps categories onto Platform tags, so configure useJUnitPlatform { includeCategories("...") } / excludeCategories("...") on the Test task.

saying these in an interview costs you the question

  • Believing Vintage lets JUnit 4 tests use Jupiter extensions or @ParameterizedTest.
  • Mixing org.junit.Test and org.junit.jupiter.api.Test in one class and expecting both to run.
  • Assuming @Category can't be filtered under the Platform — it maps to tags.

context