I'm writing tests for my webserver, and I'm making use of MockWebServer
(okhttp3.mockwebserver
) to mock the responses. As I understand it from the documentation, there are broadly 2 ways to do this:
mockWebServer.enqueue(...)
)I find myself in a position where I need to use both of these approaches.
The dispatcher is much more efficient than enqueueing each request for each test, but it can't be used for testing a retry mechanism (like Retry.backoff(3, Duration.ofSeconds(10)))
). Enqueueing requests is easy, but tedious for every other test that does not involve retry.
Right now, the only solution I can think of is to use 2 separate MockWebServer
s for the 2 types of tests (one using enqueue for retry based tests, the other using a dispatcher for remaining API tests). This seems suboptimal, especially because the dispatcher init()
is performed before every test (@BeforeAll
), so the retry tests would have 2 MockWebServers
simultaneously.
Is there any way to combine both these approaches into a single one? I'm basically looking for something along the lines of:
@Override
public MockResponse dispatch(RecordedRequest req) throws InterruptedException {
return switch (req.getPath()) {
case "/query1" -> new MockResponse().setResponseCode(200).setBody(...);
case "/query2" -> new MockResponse().setResponseCode(200).setBody(...);
case "/query3" -> server.enqueue(new MockResponse().setResponseCode(502));
server.enqueue(new MockResponse().setResponseCode(502));
server.enqueue(new MockResponse().setResponseCode(200).setBody(...));
In this example, I want requests to /query1
and /query2
to always return the specified responses, but for /query3
I want those 3 specific responses in that order.
But this obviously doesn't work because the Dispatcher's dispatch(...)
method needs a single MockResponse return value.
I'm also restricted to using the libraries already in the project, so the answer to this similar question doesn't work for me (also that library hasn't been updated in over 5 years).
You could implement a Dispatcher
where you add actions to a list, each with a predicate to match the request. Once an action has matched you discard it so the next action can be applied.
public class MyDispatcher implements Dispatcher {
public static class MyDispatcherEntry {
private Predicate<RecordedRequest> predicate;
private Function<RecordedRequest, MockResponse> action;
// TODO constructor
}
private final List<MyDispatcherEntry> entries = new LinkedList<>();
public void enqueue(String path, MockResponse response) {
enqueue(req -> req.getPath().equals(path), req -> response);
}
public void enqueue(
Predicate<RecordedRequest> predicate,
Function<RecordedRequest, MockResponse> action
) {
entries.add(new MyDispatcherEntry(predicate, action));
}
public MockResponse dispatch(RecordedRequest req) {
for (Iterator<MyDispatcherEntry> it = entries.iterator(); it.hasNext(); ) {
MyDispatcherEntry entry = it.next();
if (entry.predicate.test(req)) {
it.remove(); // discard this action so another can match next time
return entry.action.apply(req);
}
}
throw new RuntimeException("No matching entries for " + req);
}
}
Usage
MyDispatcher dispatcher = new MyDispatcher();
dispatcher.enqueue("/query1", new MockResponse().setResponseCode(200).setBody(...));
dispatcher.enqueue("/query2", new MockResponse().setResponseCode(200).setBody(...));
dispatcher.enqueue("/query3", new MockResponse().setResponseCode(502));
dispatcher.enqueue("/query3", new MockResponse().setResponseCode(502));
dispatcher.enqueue("/query3", new MockResponse().setResponseCode(200).setBody(...));