Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking server results on Android (Wiremock, MockWebServer etc)

Does anyone have a full and recent tutorial or project on recording server responses- get and post and headers as needed AND Playing them back with wiremock and/or mockwebserver?

I've looked at many out there already

like image 482
nAndroid Avatar asked Oct 31 '22 01:10

nAndroid


1 Answers

I had implemented one at my current project. First of all, i created a MockHTTPDispatcher class that extends Dispatcher class from OkHttp3. The Matcher here is imported from Hamcrest.

public class MockHTTPDispatcher extends Dispatcher {

    private Map<Matcher<RecordedRequest>, MockResponse> requestResponseMap;

    public MockHTTPDispatcher() {
        requestResponseMap = new HashMap<>();
    }

    public MockHTTPDispatcher mock(Matcher<RecordedRequest> matcher, MockResponse mockResponse) {
        requestResponseMap.put(matcher, mockResponse);
        return this;
    }

    @Override
    public MockResponse dispatch(RecordedRequest recordedRequest) {
        String recordedRequestPath = recordedRequest.getPath();
        for (Matcher<RecordedRequest> mockRequest : requestResponseMap.keySet()) {
            if (mockRequest.matches(recordedRequest)) {
                MockResponse response = requestResponseMap.get(mockRequest);
                return response;
            }
        }
        //means unable to find path for recordedRequestPath
        return new MockResponse().setResponseCode(404);
    }

    //you can also create a clear() method to clear the requestResponseMap if needed
}

Then, I created a MockWebServerRule class that implements TestRule. This bunch of code will cover a case when you want to set headers and when you don't.

public class MockWebServerRule implements TestRule {
    public static final String DOMAIN = "localhost";
    public static final int PORT = 4567;
    private MockHTTPDispatcher mockHTTPDispatcher;
    private MockWebServer mockWebServer;

    public MockWebServerRule() {
        mockWebServer = new MockWebServer();
        mockHTTPDispatcher = new MockHTTPDispatcher();
        mockWebServer.setDispatcher(mockHTTPDispatcher);
    }

    @Override
    public Statement apply(Statement statement, Description description) {
        return new MockHTTPServerStatement(statement);
    }

    public void mockResponse(String path, String httpMethod, int httpResponseCode, String response) throws Exception {
        mockResponseWithHeaders(path, httpMethod, httpResponseCode, response, null);
    }

    public void mockResponseWithHeaders(String path, String httpMethod, int httpResponseCode,
                                        String response, Headers headers) throws Exception {
        mock(allOf(pathStartsWith(path), httpMethodIs(httpMethod)), httpResponseCode, response, headers);
    }

    public void mock(Matcher<RecordedRequest> requestMatcher, int httpResponseCode, String response, Headers headers) throws IOException {
        MockResponse mockResponse = new MockResponse()
                .setResponseCode(httpResponseCode)
                .setBody(response);
        if (headers != null)
            mockResponse.setHeaders(headers);
        mockHTTPDispatcher.mock(requestMatcher, mockResponse);
    }

    public MockHTTPDispatcher getDispatcher() {
        return mockHTTPDispatcher;
    }

    //inner class for starting and shutting down localhost
    private class MockHTTPServerStatement extends Statement {

        private Statement base;

        public MockHTTPServerStatement(Statement base) {
            this.base = base;
        }

        @Override
        public void evaluate() throws Throwable {
            mockWebServer.start(PORT);
            try {
                this.base.evaluate();
            } finally {
                mockWebServer.shutdown();
            }
        }
    }
}

For the httpMethodIs and pathStartsWith, you need to create a method like this somewhere (i created a class named RequestMatchers for these).

    public static Matcher<RecordedRequest> httpMethodIs(final String httpMethod) {
    return new TypeSafeMatcher<RecordedRequest>() {
        @Override
        protected boolean matchesSafely(RecordedRequest item) {
            return httpMethod.equals(item.getMethod());
        }

        @Override
        public void describeTo(org.hamcrest.Description description) {
            description.appendText("getMethod should return");
        }
    };
}

And

    public static Matcher<RecordedRequest> pathStartsWith(final String path) {
    return new TypeSafeMatcher<RecordedRequest>() {
        @Override
        protected boolean matchesSafely(RecordedRequest item) {
            return item.getPath().startsWith(path);
        }

        @Override
        public void describeTo(org.hamcrest.Description description) {
            description.appendText("getPath should return");
        }
    };
}

In your instrumentation test, you can call the mock webserver rule using annotation @Rule just like this:

public class YourActivityTest {

    @Rule
    public MockWebServerRule mockWebServerRule = new MockWebServerRule();

    @Test
    public void shouldHandleResponse200() throws Exception {
        mockWebServerRule.mockResponse("/your/api/endpoint/", "GET", 200, readAsset("response_success.json"));

        //your assertion here
    }
}

You can just change the "GET" with "POST" or anything else with your expected status code response. Don't forget to add the implementation of readAsset(String fileName) somewhere in your code, so it can read json assets that you have stored.

like image 72
baskara Avatar answered Nov 09 '22 13:11

baskara