Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Route Dispatcher for MockWebServer #8115

Open
victorherraiz opened this issue Nov 26, 2023 · 4 comments
Open

Route Dispatcher for MockWebServer #8115

victorherraiz opened this issue Nov 26, 2023 · 4 comments
Labels
enhancement Feature not a bug

Comments

@victorherraiz
Copy link

I would like to have an alternative to sequential mocking and verifications. Some applications could perform calls concurrently or in a non predictable order. Or it just seems that we are testing "implementation details" when we "force" the order in the interaction. There are several solutions to this issue, like having multiple MockWebServer instances but they feel rather comvoluted.

I implemented a draft of a Route dispacher:

public class RouteDispatcher extends Dispatcher {

    private final List<Route> routes;
    private final List<IdAndRequest> requests =
        Collections.synchronizedList(new ArrayList<>());
    private final MockResponse defaultResponse;

    private RouteDispatcher(Builder builder) {
        this.routes = builder.routes;
        this.defaultResponse = builder.defaultResponse;
    }

    private record IdAndRequest(Object id, RecordedRequest request) {
    }

    @NotNull
    @Override
    public MockResponse dispatch(@NotNull RecordedRequest req) {
        for (var route : routes) {
            if (route.matcher.test(req)) {
                requests.add(new IdAndRequest(route.id, req));
                return route.response;
            }
        }
        return defaultResponse;
    }

    @FunctionalInterface
    public interface RequestMatcher extends Predicate<RecordedRequest> {
    }

    public static RequestMatcher pathStartsWith(String path) {
        return req -> {
            var reqPath = req.getPath();
            return reqPath != null && reqPath.startsWith(path);
        };
    }

    private record Route(
        Object id,
        RequestMatcher matcher,
        MockResponse response) {
    }

    public List<RecordedRequest> getRequests(Object id) {
        return requests.stream()
            .filter(r -> r.id().equals(id))
            .map(IdAndRequest::request)
            .toList();
    }

    public RecordedRequest getRequest(Object id) {
       var requests = getRequests(id);
       assertEquals("Expected exactly one request with id " + id, 1, requests.size());
       return requests.get(0);
    }

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {
        private final List<Route> routes = new ArrayList<>();
        public MockResponse defaultResponse = new MockResponse().setResponseCode(404);

        private Builder() {
            // Use the method builder() instead
        }

        public Builder addRoute(Object id, RequestMatcher matcher, MockResponse response) {
            routes.add(new Route(id, matcher, response));
            return this;
        }

        public Builder addRoute(Object id, RequestMatcher matcher, UnaryOperator<MockResponse> response) {
            return addRoute(id, matcher, response.apply(new MockResponse()));
        }

        public Builder addRoute(RequestMatcher matcher, MockResponse response) {
            routes.add(new Route(matcher, matcher, response));
            return this;
        }

        public Builder addRoute(RequestMatcher matcher, UnaryOperator<MockResponse> response) {
            return addRoute(matcher, response.apply(new MockResponse()));
        }

        public RouteDispatcher build() {
            return new RouteDispatcher(this);
        }

        public RouteDispatcher buildAndSet(MockWebServer server) {
            var dispatcher = this.build();
            server.setDispatcher(dispatcher);
            return dispatcher;
        }

    }
}

And some example of the usage:

    @Test
    void interactions_tests(@Autowired WebClient.Builder builder) {
        var path01 = pathStartsWith("/path01");
        var dispatcher = RouteDispatcher.builder()
            .addRoute(path01, res -> res.setResponseCode(200))
            .buildAndSet(server);
       // Emulation of real service call
        var response = builder.build().get().uri("http://localhost:8090/path01")
            .retrieve().toBodilessEntity().block();
        assertNotNull(response);
        assertEquals(HttpStatus.OK, response.getStatusCode());

        var request = dispatcher.getRequest(path01);
        assertEquals("/path01", request.getPath());
    }

I like this kind of feature in Wiremock but I preffer your implementation with less dependencies and included the Spring Boot BOM.

If you feel this useful, I could code that in kotlin, add the proper test and do a pull request after your evaluation.

@victorherraiz victorherraiz added the enhancement Feature not a bug label Nov 26, 2023
@yschimke
Copy link
Collaborator

We aren't very actively improving MockWebServer. If you find features that you need in Wiremock, it's probably better to go with that rather than going through the ringer of a big feature change to MockWebServer.

cc @swankjesse thoughts?

@victorherraiz
Copy link
Author

That is a pity, MockWebServer, in my opinion, is by far more convenient and it does not have tons of dependencies.

@swankjesse
Copy link
Member

I think this RouteDispatcher thing is great! But I’d prefer to omit it from the MockWebServer library. If you’d like to do a new library, please do!

@victorherraiz
Copy link
Author

OK. At the moment I am going to keep this at a shared library for test in my company, if you reconsider adding a route dispacher, let me know. I could do a pull request with tested RouteDispacher.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Feature not a bug
Projects
None yet
Development

No branches or pull requests

3 participants