Adding a MockWebServer JUnit Jupiter Extension
Posted on July 05, 2025 by Scott Leberknight
In the last post, I used several utilities in the kiwi-test library to clean up and remove boilerplate from tests using OkHttp's MockWebServer. But there's something else we can do to remove even more boilerplate from tests. The tests in the previous two blogs have the same code in the @BeforeEach and @AfterEach methods to:
- Create a new
MockWebServerinstance and set an instance field - Get the base
URIfor the server where tests can send requests - Close the server after each test completes
This setup and teardown logic can be extracted into a JUnit Jupiter extension that will:
- Before each test, create a new
MockWebServer - Provide methods to get the server instance and the base
URIof the server - After each test, close the server
Here is one implementation:
package org.kiwiproject.test.okhttp3.mockwebserver;
// imports...
public class MockWebServerExtension implements BeforeEachCallback, AfterEachCallback {
@Getter
@Accessors(fluent = true)
private MockWebServer server;
@Getter
@Accessors(fluent = true)
private URI uri;
public MockWebServerExtension() {
this(new MockWebServer());
}
public MockWebServerExtension(MockWebServer server) {
this.server = KiwiPreconditions.requireNotNull(server, "server must not be nul");
}
@Override
public void beforeEach(ExtensionContext context) throws IOException {
server = new MockWebServer();
server.start();
uri = server.url("/").uri();
}
@Override
public void afterEach(ExtensionContext context) {
KiwiIO.closeQuietly(server);
}
}
This implementation provides two constructors. The no-arg constructor creates a MockWebServer instance for you, while the one-arg constructor lets you create your own instance with any customization your tests need. For example, to support TLS.
It also provides the server() and uri() methods to easily get the MockWebServer instance and the base URI for use in your tests. Note these methods are generated usng Lombok, though they would be easy enough to create manually.
Using the extension in tests is straightforward. You add a MockWebServerExtension instance field and annotate it with @RegisterExtension:
@RegisterExtension private final MockWebServerExtension serverExtension = new MockWebServerExtension();
For convenience, you can also declare a MockWebServer field:
private MockWebServer server;
Then in your test's @BeforeEach method, you initialize the server field, which can then be referenced in tests.
@BeforeEach
void setUp() {
server = serverExtension.server();
// additional initialization code...
}
Alternatively, you can get the server in each test using the extension's server() method:
@Test
void someTest() {
var server = serverExtension.server();
// test code...
}
Since the extension takes care of closing the server, you don't need to have a custom @AfterEach method to do that.
Now, you can write a complete test that uses the extension like the following:
class MathApiTest {
@RegisterExtension
private final MockWebServerExtension serverExtension = new MockWebServerExtension();
private MathApiClient mathClient;
private Client client;
private MockWebServer server;
@BeforeEach
void setUp() {
// Create the Jersey client
client = ClientBuilder.newBuilder()
.connectTimeout(500, TimeUnit.MILLISECONDS)
.readTimeout(500, TimeUnit.MILLISECONDS)
.build();
server = serverExtension.server();
var baseUri = serverExtension.uri();
mathClient = new MathApiClient(client, baseUri);
}
@AfterEach
void tearDown() {
// Close the Jersey client
client.close();
}
@Test
void shouldAdd() {
server.enqueue(new MockResponse()
.setResponseCode(200)
.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain")
.setBody("42"));
assertThat(mathClient.add(40, 2)).isEqualTo(42);
var recordedRequest = takeRequiredRequest(server);
assertThatRecordedRequest(recordedRequest)
.isGET()
.hasPath("/math/add/40/2")
.hasNoBody();
}
// ...more tests...
}
This test's @BeforeEach method gets the MockWebServer and the base URI directly from the MockWebServerExtension. So the only initialization logic it needs to do is to create a Jersey client and an instance of the class being tested, MathApiClient. As mentioned earlier, the test doesn't need to close the server in the @AfterEach method, so all it needs to do is close the Jersey client.
Each test then is the same as the previous post, where we used RecordedReqests and RecordedRequestAssertions from kiwi-test to keep the test code clean.
And that's all there is to it! The extension code shown above provides what you need in the majority of testing situations. But you don't need to create your own or copy this code if you don't want. kiwi-test version 3.9.0 adds its own MockWebServerExtension. It is very similar to the extension show here, but adds a few additional features such as the ability to specify a "server customizer", which is a Consumer<MockWebServer> that lets you customize a server, for example, to add TLS support and only support HTTP 1.1 and 2.0:
@RegisterExtension
private final MockWebServerExtension serverExtension = new MockWebServerExtension(svr -> {
svr.setProtocols(List.of(Protocol.HTTP_2, Protocol.HTTP_1_1));
svr.useHttps(getSocketFactory(), false);
});
It also provides a uri(path) method that lets you easily get a URI relative to the base URI of the server:
var statusURI = serverExtension.uri("/status");
Wrapping Up
Using a JUnit extension like the MockWebServerExtension shown here is one more thing you can do to eliminate boilerplate code in your tests. It can also provide the flexibility needed by different tests by allowing customization of the MockWebServer.