What replaces WireMock's WireMockRule when an embedded fixture moves from JUnit 4 to JUnit 5?
answer
- the host framework changed, not the server
- two modules, one per JUnit generation
- an annotation or a registered extension
- the runtime info carries the address
- WireMockRule is the JUnit 4 form
basics
~20 sWireMockRule is WireMock's JUnit 4 integration, published in wiremock-junit4. On JUnit 5 you take wiremock-junit5 and use either the declarative @WireMockTest annotation, which injects a WireMockRuntimeInfo, or WireMockExtension when you need to configure the instance yourself.
solid answer
~50 s`WireMockRule` is WireMock's JUnit 4 integration and it ships in `wiremock-junit4`: you declare it as a rule field, hand it a `wireMockConfig()` when you want options, and JUnit 4 starts and stops the embedded server around your tests. JUnit 5's own integration point is different, so WireMock publishes `wiremock-junit5` with two entry points instead. The declarative one is `@WireMockTest` on the test class: it starts and stops an instance for you, takes a free port unless told otherwise, and any test method may declare a `WireMockRuntimeInfo` parameter to learn the resolved port and base URL. The programmatic one is `WireMockExtension`, registered on a field, which you reach for when the fixture needs options the annotation does not express. Both are the same embedded server underneath — what changes is who owns its start-up and how the test gets its address.
code
java · 25 lines// JUnit 4 - wiremock-junit4
public class LadderRuleTest {
@Rule
public WireMockRule ladderStub = new WireMockRule(wireMockConfig().dynamicPort());
@Test
public void readsTheTaysideLadder() {
ladderStub.stubFor(get(urlPathEqualTo("/ladders/tayside-a/standings"))
.willReturn(aResponse().withStatus(200).withBody("[]")));
}
}
// JUnit 5 - wiremock-junit5
@WireMockTest
class LadderAnnotationTest {
@Test
void readsTheTaysideLadder(WireMockRuntimeInfo wm) {
stubFor(get(urlPathEqualTo("/ladders/tayside-a/standings"))
.willReturn(aResponse().withStatus(200).withBody("[]")));
LadderClient client = new LadderClient(wm.getHttpBaseUrl());
assertEquals(0, client.standings("tayside-a").size());
}
}go deeper
Recognise both shapes on sight: a WireMockRule field in an older JUnit 4 test, and the @WireMockTest annotation with a WireMockRuntimeInfo parameter in a JUnit 5 one.
Explain which module each entry point lives in, what the annotation does on your behalf, and when you would register WireMockExtension instead of using the annotation.
Lead the migration: keep the stubs and the client wiring intact while the start-up mechanism changes, and know what stays identical so the diff stays small and reviewable.
Set the house pattern for embedded fixtures across many services — one shared shape or per-team freedom — and be able to justify the ongoing cost of a mixed estate.
## Two integrations, two modules The server is not what changes here. `WireMockRule` and the JUnit 5 entry points all start the same embedded WireMock instance inside the same test JVM, listening on the same kind of port, answering the same stubs. What differs is the glue that owns its start-up, and WireMock 4 publishes that glue as two separate modules: - `wiremock-junit4` — supplies `WireMockRule`. - `wiremock-junit5` — supplies the `@WireMockTest` annotation and `WireMockExtension`. Both are reachable from the aggregate `org.wiremock:wiremock` artifact, so a suite that takes the aggregate already has whichever one it needs. A suite that pins modules has to swap one for the other as part of the move. ## The JUnit 4 shape On JUnit 4 the fixture is a field. You construct `new WireMockRule(wireMockConfig().dynamicPort())` — the same configuration object an embedded `WireMockServer` takes — and JUnit 4's rule mechanism starts and stops it around the tests. Because the `WireMockRule` field *is* a server, the test body drives it directly: `ladderStub.stubFor(...)` for the curling-club ladder stub, and the same instance for reading its address back. That directness is the shape people remember, and it is also the reason the move feels bigger than it is: the fixture object and the server object were the same thing, so it is easy to assume that changing one changes everything. ## The JUnit 5 declarative shape `@WireMockTest` goes on the test class. It starts and stops an instance for you and takes a free port unless you tell it otherwise — which is exactly why `WireMockRuntimeInfo` exists. Any test method may declare a `WireMockRuntimeInfo` parameter, and it arrives carrying the resolved port and base URL, ready to be handed to the client under test. Stubs are written with the same static WireMock DSL as before: ``` stubFor(get(urlPathEqualTo("/ladders/tayside-a/standings")) .willReturn(aResponse().withStatus(200).withBody("[]"))); ``` This is the right default. It is the shortest thing that works, it makes the dynamic port explicit rather than incidental, and it keeps the test class free of fixture plumbing. ## The JUnit 5 programmatic shape `WireMockExtension` is the other entry point in `wiremock-junit5`, registered on a field rather than declared with an annotation. Reach for it when the annotation does not express what the fixture needs — options that have to be built up in code, or a case where the test body wants the instance itself in hand rather than only its runtime info. The rule of thumb is short: **annotation by default, extension when you need to configure or hold the instance.** ## Side by side | concern | JUnit 4 | JUnit 5 | |---|---|---| | module | `wiremock-junit4` | `wiremock-junit5` | | entry point | `WireMockRule` field | `@WireMockTest`, or `WireMockExtension` on a field | | who starts the server | the rule field | the annotation, or the registered extension | | how the test gets the address | from the rule instance | from an injected `WireMockRuntimeInfo` | | how stubs are written | unchanged | unchanged | ## What does not change This is the half candidates undersell, and it is what makes the migration tractable: 1. The WireMock stubbing DSL is identical on both sides — `stubFor(get(urlPathEqualTo(...)).willReturn(aResponse()...))` reads the same either way. 2. The configuration object is the same WireMock `wireMockConfig()`, with the same `dynamicPort()` on it. 3. The client under test needs no change at all, provided it already takes its upstream base URL as configuration. 4. The server is embedded in both cases, so nothing about processes, images or networking enters the picture. In practice the diff is confined to the fixture: a field and an annotation change, the address is obtained differently, and every stub in the file stays exactly as it was. If a migration is touching stubs, something else is going on. ## The neighbouring product In MockServer the same job is usually done by the fixture itself calling `ClientAndServer.startClientAndServer()`, which starts an in-process instance and returns a `MockServerClient` bound to it for the test to drive. ## Interview angle - Name both modules, not just the class names — that is the part people cannot produce from memory. - Say that `@WireMockTest` is the declarative default and `WireMockExtension` is what you register when you need options. - Explain that `WireMockRuntimeInfo` is how the JUnit 5 form delivers an address that only exists after start-up. - Be explicit that the stubs themselves are untouched, and use that to argue the migration is small and reviewable.
- When would you register WireMockExtension instead of annotating the class with @WireMockTest?When the fixture needs options the annotation does not express, or when the test body wants the instance itself in hand rather than only its runtime info. The extension is registered on a field and configured with the same `wireMockConfig()` options an embedded `WireMockServer` takes. `@WireMockTest` is the shorter form and the right default whenever you need nothing beyond a running server and its address.
- Does moving from WireMockRule to @WireMockTest change how stubs are written?No. Both start the same embedded WireMock server and the stubbing DSL is unchanged — `stubFor(get(urlPathEqualTo(...)).willReturn(aResponse()...))` reads the same on either side. What changes is who starts and stops the instance and how the test learns its address. That is why the migration is usually confined to the fixture and leaves every stub in the file untouched.
saying these in an interview costs you the question
- Thinks WireMockRule still works unchanged on JUnit 5
- Believes the annotation and the extension start different servers
- Cannot say which module supplies the JUnit 5 entry points
- Expects the JUnit 5 form to hand back a fixed port
- Assumes moving to JUnit 5 means rewriting every stub