- Fully declarative
WireMock
setup. - Support for multiple
WireMockServer
instances - one per HTTP client as recommended in the WireMock documentation automatically sets Micronaut environment properties. - Support for gRCP.
- Doesn't pollute the Micronaut application context with extra beans.
In your pom.xml
, simply add the wiremock-micronaut
dependency:
<dependency>
<groupId>io.github.nahuel92</groupId>
<artifactId>wiremock-micronaut</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
Use @MicronautWireMockTest
with @ConfigureWireMock
:
@MicronautWireMockTest(
@ConfigureWireMock(
name = "user-service",
properties = "user-client.url"
)
)
@Property(name = "myProp", value = "myVal")
class TodoControllerTests {
@InjectWireMock("user-service")
private WireMockServer wiremock;
@Value("${user-client.url}")
private String wiremockUrlValue; // will contain the base URL for this WireMock instance.
@Property("user-client.url")
private String wiremockUrlProperty; // will contain the base URL for this WireMock instance.
@Test
void yourSUTTest(@Property("myProp") final String myProp) {
// given
wiremock.stubFor(/*Your request*/);
// then
// execute your subject under test
// then
// your assertions...
}
}
@MicronautWireMockTest
is an enhanced@MicronautTest
annotation with WireMock capabilities, which means you can pass the same parameters as you would with@MicronautTest
.@ConfigureWireMock
creates aWireMockServer
and passes theWireMockServer.baseUrl
to a Micronaut environment property with a name given by a property@InjectWireMock
injectsWireMockServer
instances to your test
Note
WireMockServer
instances aren't added as beans to the Micronaut application context. Instead, instances
are kept in a separate store associated with the application context used by tests.
The following example shows how to use the Single Property Injection, which means each service is bound to an
exclusive WireMockServer
instance. You get maximum isolation between your services' mocks at the expense of a more
complex test setup.
@MicronautWireMockTest({
@ConfigureWireMock(
name = "foo-service",
properties = "app.client-apis.foo.base-path"
),
@ConfigureWireMock(
name = "bar-service",
properties = "app.client-apis.bar.base-path"
),
@ConfigureWireMock(
name = "mojo-service",
properties = "app.client-apis.mojo.base-path"
)
})
class YourTest {
@InjectWireMock("foo-service")
private WireMockServer fooService;
@InjectWireMock("bar-service")
private WireMockServer barService;
@InjectWireMock("mojo-service")
private WireMockServer mojoService;
@Test
void yourSUTTest() {
// your test code
}
}
The following example shows how to use the Multiple Property Injection, which means all services are bound to a shared
WireMockServer
instance. You give up on isolation between your services' mocks, but you get a less complex test setup.
@MicronautWireMockTest(
@ConfigureWireMock(
name = "services",
properties = {
"app.client-apis.foo.base-path",
"app.client-apis.bar.base-path",
"app.client-apis.mojo.base-path"
}
)
)
class YourTest {
@InjectWireMock("services")
private WireMockServer services;
@Test
void yourSUTTest() {
// your test code
}
}
Usually, you'll configure your tests as follows:
@MicronautWireMockTest({
@ConfigureWireMock(
name = "todo-client",
properties = "todo-client.url",
stubLocation = "custom-location"
),
@ConfigureWireMock(
name = "user-client",
properties = "user-client.url"
)
})
@DisplayName("WireMock server instances must be accessed via injected fields (optional if only one is needed)")
class YourTest {
@InjectWireMock("todo-service")
private WireMockServer todoService;
@InjectWireMock("user-client")
private WireMockServer userService;
@Test
void yourSUTTest() {
// given
todoService.stubFor(get("/").willReturn(ok()));
userService.stubFor(get("/").willReturn(ok()));
// your test code
}
}
Or, if you need only one server:
@MicronautWireMockTest(
@ConfigureWireMock(
name = "todo-client",
properties = "todo-client.url",
stubLocation = "custom-location"
)
)
@DisplayName("WireMock server instances must be accessed via injected fields (optional if only one is needed)")
class YourTest {
@InjectWireMock("todo-service")
private WireMockServer todoService;
@Test
void yourSUTTest() {
// given
todoService.stubFor(get("/").willReturn(ok()));
// your test code
}
}
In the previous situation, when the test only requires exactly one WireMock server instance, we can simplify it a bit.
In this case, the WireMock
client class can be used to configure your stubs:
@MicronautWireMockTest(
@ConfigureWireMock(
name = "todo-client",
properties = "todo-client.url",
stubLocation = "custom-location"
)
)
@DisplayName("When exactly one WireMock server instance is configured, it can be accessed statically via the 'WireMock' class")
class YourTest {
@Test
void yourSUTTest() {
// given
WireMock.stubFor(get("/").willReturn(ok()));
// your test code
}
}
By default, classpath location is used to get stubs:
@MicronautWireMockTest(
@ConfigureWireMock(
name = "todo-client",
properties = "todo-client.url",
stubLocation = "a-directory-on-the-classpath" // By default, the classpath is used
)
)
class YourTest {
@Inject
private TodoClient todoClient;
@Test
@DisplayName("WireMock should use a directory on the classpath as the stub location")
void yourSUTTest() {
// when
final var results = todoClient.findAll();
//then
// your test assertions
}
}
But sometimes you may want to use any directory on the file system.
To achieve that, you can override a property called stubLocationOnClasspath
on the @ConfigureWireMock
:
@MicronautWireMockTest(
@ConfigureWireMock(
name = "todo-client",
properties = "todo-client.url",
stubLocation = "a-directory-on-the-file-system",
stubLocationOnClasspath = false
)
)
class YourTest {
@Inject
private TodoClient todoClient;
@Test
@DisplayName("WireMock should use a directory on the file system as the stub location")
void yourSUTTest() {
// when
final var results = todoClient.findAll();
//then
// your test assertions
}
}
gRPC testing is very similar to previous examples, but:
- It requires extra configuration in your project (not showed here) to compile your
.proto
files. - It requires a service descriptor file for each
.proto
service you want to test.
In the following example, WireMock is instructed to:
- Internal WireMock server and gRPC service are set with the same name (
GreeterGrpc.SERVICE_NAME
, which comes from the generated code from the.proto
file). - Load the Grpc extension that enables gRPC support.
- Search for service descriptor files under
src/test/resources/wiremock
.
@MicronautWireMockTest({
@ConfigureWireMock(
name = GreeterGrpc.SERVICE_NAME,
portProperty = "my.port",
properties = "my.server",
extensionFactories = GrpcExtensionFactory.class,
stubLocation = "src/test/resources/wiremock"
)
})
public class GrpcTest {
@Inject
private GreeterGrpc.GreeterBlockingStub greeter;
@InjectWireMock(GreeterGrpc.SERVICE_NAME)
private WireMockGrpcService greeterGrpcService;
@Test
@DisplayName("WireMock should allow configuring single gRPC service per test")
void successOnTestingWithSingleGrpcService() {
// given
createGreeterStub();
// when
final var message = greeter.sayHello(HelloRequest.newBuilder().setName("Tom").build());
// then
assertThat(message.getMessage()).isEqualTo("Hello Tom!");
}
}
It also supports multiple gRPC and HTTP stubs at the same time, although you may want to stick to simpler tests:
@MicronautWireMockTest({
@ConfigureWireMock(
name = GreeterGrpc.SERVICE_NAME,
portProperty = "my.port",
properties = "my.server",
extensionFactories = GrpcExtensionFactory.class,
stubLocation = "src/test/resources/wiremock"
),
@ConfigureWireMock(
name = Greeter2Grpc.SERVICE_NAME,
portProperty = "my.port2",
properties = "my.server2",
extensionFactories = GrpcExtensionFactory.class,
stubLocation = "src/test/resources/wiremock2"
),
@ConfigureWireMock(name = "user-client", properties = "user-client.url")
})
class GrpcAndHttpTest {
@Inject
private GreeterGrpc.GreeterBlockingStub greeter;
@Inject
private Greeter2Grpc.Greeter2BlockingStub greeter2;
@InjectWireMock(GreeterGrpc.SERVICE_NAME)
private WireMockGrpcService greeterGrpcService;
@InjectWireMock(Greeter2Grpc.SERVICE_NAME)
private WireMockGrpcService greeter2GrpcService;
@Inject
private UserClient userClient;
@Test
@DisplayName("WireMock should allow configuring multiple gRPC and HTTP services per test")
void successOnTestingWithGrpc() {
// given
greeterGrpcService.stubFor(method("sayHello")
.withRequestMessage(equalToMessage(HelloRequest.newBuilder().setName("Tom")))
.willReturn(message(HelloReply.newBuilder().setMessage("Hello Tom!")))
);
greeter2GrpcService.stubFor(method("sayHello2")
.withRequestMessage(equalToMessage(HelloRequest2.newBuilder().setName("Nahuel")))
.willReturn(message(HelloReply2.newBuilder().setMessage("Hello Nahuel!")))
);
final var result = userClient.findOne(1L);
// when
final var message = greeter.sayHello(HelloRequest.newBuilder().setName("Tom").build());
final var message2 = greeter2.sayHello2(HelloRequest2.newBuilder().setName("Nahuel").build());
// then
try (final var softly = new AutoCloseableSoftAssertions()) {
softly.assertThat(message.getMessage()).isEqualTo("Hello Tom!");
softly.assertThat(message2.getMessage()).isEqualTo("Hello Nahuel!");
softly.assertThat(result.name()).isEqualTo("Jenna");
}
}
}
More test examples can be found in the example
module.
WireMock extensions can be registered independently with each @ConfigureWireMock
:
@ConfigureWireMock(name = "...", property = "...", extensions = {/*...*/})
By default, each WireMockServer
is configured to load mapping files from a classpath directory
wiremock/{server-name}/mappings
.
It can be changed with setting stubLocation
on @ConfigureWireMock
:
@ConfigureWireMock(name = "...", property = "...", stubLocation = "my-stubs")
This extension was inspired (and based) on WireMock Spring Boot by Maciej Walkowiak and Stefano Cordio.
I encourage you to support them; and, if you appreciate the time and effort I put into making this extension for Micronaut, please consider to sponsor this project (WIP).
Thank you 😊