wiremock-micronaut
Simplifies testing HTTP clients in Micronaut 5 and above. It starts WireMock servers per test class, binds their URLs into Micronaut configuration before your beans are created and injects them into your tests. The annotations mirror wiremock-spring-boot, so it should feel familiar.
WireMock for Micronaut 5 tests: start WireMock servers per test class, bind their URLs into Micronaut configuration before your beans are created, and inject them into your tests. Annotations and attributes mirror wiremock-spring-boot.
Requirements
- Java 25
- Micronaut 5.2.1 or later (5.x) with
micronaut-test-junit5 - Building this repo needs Gradle running on JDK 25 (see
.sdkmanrc)
Install
testImplementation("io.github.leeturner:wiremock-micronaut:<version>")
The latest <version> is on the Maven Central badge above.
The library brings wiremock-standalone (a shaded jar), so WireMock’s own
dependencies never clash with Micronaut’s.
Quick start (Java)
@MicronautTest
@EnableWireMock(@ConfigureWireMock(name = "users", baseUrlProperties = "users.url"))
class UsersClientTest {
@Inject UsersClient client; // @Client("${users.url}")
@InjectWireMock("users") WireMockServer users;
@Test
void fetchesAUser() {
users.stubFor(get("/users/1").willReturn(ok("alice")));
assertThat(client.user("1")).isEqualTo("alice");
}
}
Quick start (Kotlin)
@MicronautTest
@EnableWireMock(ConfigureWireMock(name = "users", baseUrlProperties = ["users.url"]))
class UsersClientTest {
@Inject lateinit var client: UsersClient
@InjectWireMock("users") lateinit var users: WireMockServer
}
Mocking Micronaut HTTP clients
Both ways of declaring a client work.
URL placeholder. Bind the property the placeholder uses:
@Client("${users.url}") // or "${micronaut.http.services.users.url}"
public interface UsersClient { /* ... */ }
@EnableWireMock(@ConfigureWireMock(name = "users", baseUrlProperties = "users.url"))
Service id. Bind the service’s URL property. WireMock’s value overrides
the real URL in your application.yml/application.properties for the
test:
@Client("setlist-fm")
public interface SetlistFmClient { /* ... */ }
@EnableWireMock(
@ConfigureWireMock(
name = "setlist-fm",
baseUrlProperties = "micronaut.http.services.setlist-fm.url"))
If your configuration uses micronaut.http.services.<id>.urls (a list),
bind that key instead.
How it works
- A bare
@EnableWireMockstarts one server namedwiremock. - Its properties (
wiremock.server.port,wiremock.server.baseUrl, and the HTTPS equivalents when enabled) are bound into the Micronaut context before any bean is created, so@Client("${...}"),@Valueand eager beans all see them. - Servers are reset before each test (
resetWireMockServer). When exactly one server is configured, the staticWireMock.stubFor(...)DSL points at it. @Nestedclasses share their enclosing class’s servers.@MicronautTest(rebuildContext = true)keeps the same servers and ports.- Servers stop after the test class, even if the context fails to start.
@ConfigureWireMock attributes
| Attribute | Default | Meaning |
|---|---|---|
name |
"wiremock" |
Server name for @InjectWireMock. |
port |
0 |
HTTP port: 0 dynamic, -1 disabled. |
httpsPort |
-1 |
HTTPS port: -1 disabled, 0 dynamic. |
portProperties |
wiremock.server.port |
Properties set to the HTTP port. |
httpsPortProperties |
wiremock.server.httpsPort |
Properties set to the HTTPS port. |
baseUrlProperties |
wiremock.server.baseUrl |
Properties set to http://localhost:<port>. |
httpsBaseUrlProperties |
wiremock.server.httpsBaseUrl |
Properties set to https://localhost:<httpsPort>. |
filesUnderClasspath |
"" |
Classpath root with mappings/__files/message-mappings. |
filesUnderDirectory |
{} |
Directories with mappings/__files/message-mappings (first existing wins; takes precedence over classpath). |
extensions / extensionFactories |
{} |
WireMock extensions (public no-arg constructor). |
configurationCustomizers |
{} |
WireMockConfigurationCustomizers, applied last. |
resetWireMockServer |
true |
Reset before each test. |
registerBean |
false |
Also expose the server as @Named("<name>") WireMockServer. |
globalTemplating |
false |
Response templating for every stub. |
keystore*, trustStore*, needClientAuth |
— | HTTPS/TLS settings. |
With no files location configured, stubs load from the first existing
directory in wiremock, stubs, mappings, src/test/resources/wiremock,
src/test/resources/stubs, src/test/resources/mappings (and the
src/integtest/resources/... equivalents).
Several servers
@MicronautTest
@EnableWireMock({
@ConfigureWireMock(name = "users", baseUrlProperties = "users.url"),
@ConfigureWireMock(name = "orders", baseUrlProperties = "orders.url")
})
class CheckoutTest { /* ... */ }
Default property names shared by several servers are not bound, because they would be ambiguous. Give each server its own property names. Explicit duplicates fail fast.
Message stubs and server-sent events
WireMock’s message stubs and SSE need no extra configuration. Put message
stubs in message-mappings, next to mappings:
stubs-by-server/live-setlist/
mappings/live.json # opens the SSE stream, plus a trigger stub
message-mappings/live-setlist.json # what to send when the trigger is hit
An HTTP stub opens the stream:
{ "request": { "method": "GET", "urlPath": "/gigs/radiohead-2026/live" },
"response": { "status": 200, "openSseChannel": true } }
A message stub sends events to it when another stub (by id) is hit:
{ "trigger": { "type": "http-stub", "stubId": "cccccccc-0000-4000-8000-000000000001" },
"actions": [ { "type": "send",
"message": { "body": { "data": "Airbag" }, "headers": { "event": "song" } },
"channelTarget": { "type": "request-initiated", "channelType": "sse",
"requestPattern": { "urlPath": "/gigs/radiohead-2026/live" } } } ] }
The Java DSL works on an injected server or, with one server, the static
WireMock methods:
liveSetlist.messageStubFor(
message()
.triggeredByHttpRequest(newRequestPattern().withUrl(urlPathEqualTo("/start")))
.willTriggerActions(
sendSse("Airbag").withEventName("song")
.onChannelsMatching(newRequestPattern().withUrl(urlPathEqualTo("/live")).build())));
liveSetlist.waitForMessageEvent(
messagePattern().withBody(equalTo("Airbag")).build(), Duration.ofSeconds(5));
- Message stubs and the message journal are reset before each test, like
HTTP stubs. Stubs from
message-mappingsare reloaded. - Trigger events only after your client has connected. Events sent before
then have no channel and are dropped. Note the size of
server.listAllMessageChannels().getChannels()before connecting and wait for it to grow. Don’t just wait for it to be non-empty: a disconnected SSE channel stays listed until a send to it fails. - To consume SSE with a Micronaut declarative client, use the Netty
io.micronaut:micronaut-http-client. The JDK client does not support SSE. - WebSockets work the same way, with
openWebsocketChanneland"channelType": "websocket".
Gotchas
@EnableWireMock/@ConfigureWireMockbelong on the top-level test class, not on@Nestedclasses.- Without
@MicronautTest, servers still run and inject, but nothing is bound into Micronaut configuration. @InjectWireMockis not a jakarta@Qualifier. In a class with anyregisterBeanserver, test-method parameters must use@Named("<name>")(@InjectWireMockfields still work);@InjectWireMockparameters fail fast there.- Apps using Micronaut declarative HTTP clients need a JSON module on the
runtime classpath, for example
io.micronaut.serde:micronaut-serde-jackson. The examples add it.
Pyronaut
Pyronaut apps are Micronaut apps written in Python.
The extension works in their JUnit 5 tests, whether written in Java
(test-java/) or as Python test modules. pytest tests don’t run through
JUnit, so the extension does nothing there.
[tool.pyronaut.dependencies]
test = [
"io.micronaut.test:micronaut-test-junit5",
"io.github.leeturner:wiremock-micronaut:<version>",
]
[tool.pyronaut.test]
engine = "junit" # or "both" to run pytest tests too
A Python test module calls the annotations at module level:
MicronautTest()
EnableWireMock(
ConfigureWireMock(name="users", baseUrlProperties=["users.url"], registerBean=True))
client: Annotated[UsersClient, Inject]
users: Annotated[WireMockServer, Inject, Named("users")]
@Test
def test_fetches_a_user():
users.stubFor(WireMock.get("/users/1").willReturn(WireMock.ok("alice")))
assert str(client.user("1")) == "alice"
@InjectWireMockdoes nothing in a Python module, because Pyronaut only injects module attributes markedInject. SetregisterBean=Trueand inject the server withNamed("<name>"), as above. With a single server, the staticWireMock.stubFor(...)also works.- Python can’t import Java static methods: write
WireMock.get(...), notget(...). - Annotation attributes keep their Java names (
baseUrlProperties,startApplication). Array attributes take lists. filesUnderClasspathlooks in Pyronaut’s test resources (tests-config/). This needs 0.2.0 or later. Default stub directories are relative to the project, for examplewiremock/.- In pytest, start a
WireMockServerin a fixture and pass itsbaseUrl()toMicronautTest(properties=...).
Examples
examples/java, examples/kotlin and
examples/pyronaut are the same small Micronaut
service: GET /artists/{mbid} combines an artist
from MusicBrainz (a ${musicbrainz.url} client) with recent setlists from
setlist.fm (a setlist-fm service id client), and LiveSetlistService
follows a gig’s setlist from an SSE feed (a live-setlist service id
client). Each test class shows one way to stub them:
| Test | Technique |
|---|---|
ProgrammaticStubsTest |
Stubs in the test with stubFor, verify of request headers, MusicBrainz 404, setlist.fm 404 (no setlists), 500 and connection faults. |
ClasspathStubsTest |
One classpath folder per server (filesUnderClasspath), bodies from __files via bodyFileName, and a test stub overriding a file stub. |
DefaultDirectoryTest |
No files configuration: stubs load from src/test/resources/wiremock (wiremock/ in the Pyronaut example). That directory is shared by every such server. |
LiveSetlistTest |
SSE: mappings opens the stream, message-mappings sends the songs when a trigger stub is hit, and waitForMessageEvent verifies what was sent. |
The Pyronaut example uses this repo’s code, not a release. Run it with
GraalPy on your PATH:
./gradlew publishToMavenLocal
cd examples/pyronaut
pip install pyronaut && pyronaut setup
graalpy -m venv .venv && pyronaut install && pyronaut test
Migrating from io.github.nahuel92:wiremock-micronaut
| Before | After |
|---|---|
@MicronautWireMockTest(...) |
@MicronautTest + @EnableWireMock(...) |
@ConfigureWireMock(properties = "x") |
@ConfigureWireMock(baseUrlProperties = "x") |
portProperty = "x" |
portProperties = "x" |
package io.github.nahuel92.wiremock.micronaut |
com.leeturner.wiremock.micronaut |
gRPC (WireMockGrpcService) |
Not supported |
Versioning
0.x while WireMock 4 is in beta. 1.0.0 will follow WireMock 4 GA.