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
MockWebServer
instance and set an instance field - Get the base
URI
for 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
URI
of 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
.