Whole-Microservice Integration Tests (@MicroserviceTest)
@MicroserviceTest is a JUnit 5 extension that boots a whole Microservice — config, lifecycle, and an embedded Jetty server on an ephemeral port — for the duration of a test class, then stops it cleanly. It's the standalone-microservice analog of Spring's @SpringBootTest, and it composes the existing @TestBean mock-bean substrate for collaborator substitution.
It ships in a small dedicated support module:
<dependency>
<groupId>org.apache.juneau</groupId>
<artifactId>juneau-microservice-test</artifactId>
<version>${juneau.version}</version>
<scope>test</scope>
</dependency>
When to use it (vs. MockRestClient)
The two integration-test paths are intentionally distinct:
| Use | For |
|---|---|
@MicroserviceTest | A genuine full-microservice test — real server, real connectors, real lifecycle, exercised over HTTP. |
MockRestClient | An in-JVM test of a single @Rest resource — no server, no sockets, dispatched directly against a RestContext. |
MockRestClient does not talk to a booted server; reach for @MicroserviceTest when you want the actual server, filters, and connector path in the loop.
Specifying the system under test
The SUT is declared explicitly (no classpath scanning), two complementary ways:
configurations={...}— one or more@Configurationclasses whose@Bean Servletmethods the microservice auto-mounts (the common case).- A
static Microservice.Buildersupplier method on the test class (default namemicroserviceBuilder, override viabuilderMethod) — for full control of the builder. Anyconfigurations={...}are appended to whatever the supplier returns.
Either way the extension additionally installs an ephemeral-Jetty Server (bound to port 0) and JettyConfiguration, so tests only contribute their resources. The extension currently boots Jetty.
@MicroserviceTest(configurations = MyServiceTest.AppConfig.class)
class MyServiceTest {
@Configuration
public static class AppConfig {
@Bean Greeter greeter() { return new ProductionGreeter(); }
@Bean Servlet greetingResource(Greeter greeter) { return new GreetingResource(greeter); }
}
@Test
void greetingEndpoint(RestClient client) throws Exception {
var resp = client.get("/greeting").run();
assertEquals(200, resp.getStatusCode());
}
}
What gets injected into tests
Test methods (and lifecycle methods) may declare parameters the extension resolves:
RestClient— a real HTTP client bound to the booted server's root URL (the primary convenience).Microservice— the booted instance.WritableBeanStore— the booted instance's bean store.int/Integer— the bound (ephemeral) port.
Mock-bean injection
Substitute collaborators with the existing @TestBean (the @MockBean analog) — there is no parallel injection annotation. The default is Mode INJECT: the override is installed via Microservice.Builder.overridingBeanStore(...) before boot, so the service reads it from startup (the correct mode for whole-service tests, since the microservice resolves its beans at boot).
@MicroserviceTest(configurations = AppConfig.class)
class MyServiceTest {
// Substituted before the microservice boots — the real GreetingResource sees this mock.
@TestBean(scope = Scope.CLASS)
static Greeter mockGreeter() {
return () -> "mocked";
}
@Test
void usesTheMock(RestClient client) throws Exception {
assertEquals("mocked", client.get("/greeting").run().getBodyAsString());
}
}
@TestBean(mode = Mode.OVERLAY) instead pushes the overrides onto the already-booted instance's bean store (and pops them in teardown) — the opt-in path for per-test substitution against a long-running instance. Note that OVERLAY only affects beans the framework re-resolves through the bean store per call; beans pinned into per-op memoizers at boot need INJECT.
Lifecycle and isolation
The microservice is booted once per test class (beforeAll) and stopped in afterAll — a fresh instance per class, never reused. Microservice restart is unsupported (its stopped flag is one-way, the bean store is closed on stop, and each start() registers a JVM shutdown hook), so the build→start→stop cycle is repeated per class. stop() also restores the process globals the microservice sets (getInstance(), Config system default, Settings sources), so multiple @MicroserviceTest classes in one JVM stay isolated. Do not boot microservices in parallel within a JVM — they would stomp those globals.
The bound port is always read from the live ServerConnector (never hard-coded), so ephemeral-port binding never collides.