Mocking your server

By | April 19, 2013

Have you ever felt the the way of testing the client, especially error tests, can be hard and error prone due to modifications of the real server?

That can be omitted by creating a simple server that mocks the real one and is easy to use directly in your unit test.

I did so in a project that I was involved for a while ago. This was just to test the error state of the server. This was based on a number of “So what if” statements like “so what if the server gives a stack trace as result?”.

To do this I used a simple jetty server and deployed a simple service that maps to every possible url. I made it possible to say what url I expected the client to connect to, every other url will give a 404 as response. I set what the expected return value will be and if it expected some get/post parameters to be checked.

This turned out well and I simply could integrate the server with the ordinary unit test suites. The jetty server deployed in just millisecs so that was really good.

Because I have full control of what the expected input should be and what the produced output will be it was simple to test all “So what if” questions that the client can have (that I felt was relevant for this test).

The mocked server could easily by extended with a validation method the validates if the expected requests was received. If validation fails it will assert instead and make the test go wrong in true mock fashion.

So here is the code for this mock server, it the same as the Lightweight testing of webservice with jetty with some small extensions.

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.junit.Ignore;
import org.mortbay.jetty.Handler;
import org.mortbay.jetty.HttpConnection;
import org.mortbay.jetty.Request;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.handler.AbstractHandler;

import static javax.servlet.http.HttpServletResponse.SC_NOT_ACCEPTABLE;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static javax.servlet.http.HttpServletResponse.SC_OK;

/**
* A server for answering HTTP requests with test response data. Example taken from
* http://olafsblog.sysbsb.de/lightweight-testing-of-webservice-http-clients-with-junit-and-jetty/
*/
@Ignore
public class HttpTestServer {

    public static final int HTTP_PORT = 8080;

    private Server server;
    private String responseBody;
    private String requestBody;
    private String mockResponseData;

    private String expectedTarget;
    private String expectedMethod;
    private int forcedStatus = SC_OK;

    public HttpTestServer() {
    }

    public HttpTestServer(String mockData) {
        setMockResponseData(mockData);
    }

    public void start() throws Exception {
        configureServer();
        startServer();
    }

    private void startServer() throws Exception {
        server.start();
    }

    protected void configureServer() {
        server = new Server(HTTP_PORT);
        server.setHandler(getMockHandler());
    }

    /**
    * Creates an {@link AbstractHandler handler} returning an arbitrary String as a response.
    *
    * @return never <code>null</code>.
    */
    public Handler getMockHandler() {
        Handler handler = new AbstractHandler() {

            @Override
            public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch)
                    throws IOException, ServletException {

                Request baseRequest = request instanceof Request ? (Request) request
                        : HttpConnection.getCurrentConnection().getRequest();

                if (!target.equals(expectedTarget)) {
                    response.setStatus(SC_NOT_FOUND);
                } else if (!request.getMethod().equals(expectedMethod)) {
                    response.setStatus(SC_NOT_ACCEPTABLE);
                } else {
                    // Set the forced status (default is SC_OK)
                    response.setStatus(forcedStatus);
                }

                setResponseBody(getMockResponseData());

                setRequestBody(convertToString(baseRequest.getInputStream()));
                response.setContentType("text/html;charset=utf-8");
                write(getResponseBody(), response.getOutputStream());
                baseRequest.setHandled(true);
            }
        };
        return handler;
    }

    public void stop() throws Exception {
        server.stop();
    }

    public void setResponseBody(String responseBody) {
        this.responseBody = responseBody;
    }

    public String getResponseBody() {
        return responseBody;
    }

    public void setRequestBody(String requestBody) {
        this.requestBody = requestBody;
    }

    public String getRequestBody() {
        return requestBody;
    }

    public static void main(String[] args) {
        HttpTestServer server = new HttpTestServer();
        try {
            server.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void setMockResponseData(String mockResponseData) {
        this.mockResponseData = mockResponseData;
    }

    public String getMockResponseData() {
        return mockResponseData;
    }

    public void setExpectedTarget(String target) {
        expectedTarget = target;
    }

    public void setExpectedMethod(String method) {
        expectedMethod = method;
    }

    public void setForcedResponceStatus(int status) {
        forcedStatus = status;
    }

    protected Server getServer() {
        return server;
    }

    private void write(String data, OutputStream output) throws IOException {
        if (data != null) {
            output.write(data.getBytes());
        }
    }

    private String convertToString(InputStream input) throws IOException {
        StringBuilder buffer = new StringBuilder();
        InputStreamReader reader = new InputStreamReader(input);
        copyData(reader, buffer);
        return buffer.toString();
    }

    private long copyData(Reader input, StringBuilder output) throws IOException {
        char[] buffer = new char[4096];
        long count = 0;
        int n = 0;
        while (-1 != (n = input.read(buffer))) {
            output.append(buffer, 0, n);
            count += n;
        }
        return count;
    }
}

To use this, simply create a unit test. In the setup phase, create a new server and start it. Stop it in the teardown phase. So below is a simple example of this.

public class ClientTest {

    private HttpTestServer testServer = new HttpTestServer();

    @Before
    public void initServer() throws Exception {
        testServer.start();
    }

    @Test
    public void callServer() throws Exception {
        testServer.setMockResponseData("Test");
        String responce = new Client().callServer();

        assertEquals("Test", responce);
    }

    @After
    public void shutdownServer() throws Exception {
        testServer.stop();
    }
}

I hope that this has been somewhat useful, it work well for me in that case of testing a rest client without using the real server.

Leave a Reply

Your email address will not be published.