A Day In The Lyf

…the lyf so short, the craft so longe to lerne

Archive for February 2014

Stubbing a Mule TCP connector with mountebank

with one comment

My current project involves integration with a Mule service bus and a number of endpoints responding over a TCP connector.  We have limited control over the downstream service, but depend on it deeply, as our REST service is stateless.  

We like to QA our stories using black-box testing of our API, which means sending an HTTP request to our API and expecting it to make the relevant TCP service call.  For some time now, our biggest testing problem has been manufacturing test data for all scenarios.  Were it an HTTP dependency, stubbing would have been easy, but no TCP stubbing solution existed until recently.

Our most versatile approach was also quite kludgy.  We had created Endpoint classes that encapsulated the calls to all downstream dependencies, and had a parallel set of StubEndpoint classes.  When we deployed our API, we had the ability of specifying a different Spring profile that only wired up the StubEndpoint classes. This meant that our stubbing logic was directly housed in our production application.

stub-spring-profile

This is obviously an inelegant solution for our main application, since it now contained a significant amount of code that should never be used in production. Lest you think that having a switch to turn on what should be dead code in production isn’t dangerous, perhaps you should read about Knight Capital, which lost $465 million due to just such a bug.

Perhaps less obviously, this was also a terrible solution for our tests. Let’s say that we were testing an API retrieving travel plans, and we needed to test several different scenarios – plans in the past, cancelled plans, overbooked plans, etc. We might pass a plan number into our API, and then have to code a massive if-else block in our StubEndpoint that switched on the the plan number to manufacture the various scenarios. We might, for example, send plan 12345 for a cancelled flight and plan 23456 for an overbooked plan. The data setup was completely opaque to the tests, and the cognitive distance between the test data setup and the test scenario opened the door for some difficult maintenance and ugly interactions between tests.

We made another attempt at stubbing using Byteman, which allows you to inject Java code into a running agent. We would have to spin up the real service alongside a Byteman agent for this to work:

stub-byteman

This had a few big problems. First, it required us to actually stand up the real service, which added some tax to our development time when we wanted to isolation test our API. Second, it was very hard to use. Byteman takes a string written in a special syntax to inject Java code at a certain method in the remote application. Writing the string was fiddly at best. Finally, this approach deeply coupled our code to the remote service code. We had to know intimate details of the scope within which our injected code would run to correctly write the remote Java code. In the end, it proved too difficult to use.

mountebank made remote stubbing a breeze. Each test can now setup the test data specific to the scenario it represents. A class level tearDown or setUp can remove existing stub data before each test runs.

stub-mountebank

Each test now has full control to specify the result of the RPC call, and, unlike the Byteman approach, they can do so directly in Java code. Once we moved the mountebank interaction into a thin plumbing layer, the test code remained as clean as testing in-process with mockito.

Here’s an example test, which validates what happens through our API when we request a cancelled travel plan:


@Test
public void shouldSomethingWithCancelledTravelPlan() {
    // This is a much bigger object graph than represented here
    TravelPlan cancelledTravelPlan = new TravelPlan("1234", "2013-02-15").withStatus(CANCELLED); 

    // Now we tell mountebank to return the specified object when our app connects to the service port
    setupBinaryStub(cancelledTravelPlan, TRAVEL_PLAN_ENDPOINT_PORT);

    HttpResponse response = callOurAPIAt("http://localhost:8080/api/travelPlans/1234&date=2013-02-15");

    assertSomething(response);

    // This could be moved to a setUp or tearDown
    // I won't show the code, but it just sends an HTTP DELETE to http://localhost:2525/imposters/{port}
    tearDownStub(TRAVEL_PLAN_ENDPOINT_PORT);
}

The actual stub setup is in the seupBinaryStub method. Let’s take a look at it:


private void setupBinaryStub(Object stub, int port) {
    HttpPost createStub = new HttpPost("http://localhost:2525/imposters");
    createStub.setHeader("Content-Type", "application/json");
   
    String json = "{\n" +
                "  \"protocol\": \"tcp\",\n" +
                "  \"port\": " + port + ",\n" +
                "  \"mode\": \"binary\",\n" +
                "  \"stubs\": [\n" +
                "    { \"responses\": [{ \"is\": { \"data\": \"" + encodedString(rawObject) + "\" } }] }\n" +
                "  ]\n" +
                "}";

    createStub.setEntity(new StringEntity(buildJsonContent(expectation)));
    final HttpResponse response = new DefaultHttpClient().execute(createStub);

    if (response.getStatusLine().getStatusCode() != 201) {
        throw new RuntimeException("expected 201 status but was " + response.getStatusLine().getStatusCode());
    }
}

The magic sauce is in the encodedString method. Here we have to figure out how our remote service serializes the object graph, and encode it as a base64 string. Figuring out the serialization mechanism can be the trickiest part of all of this. In our case, a short investigation into the code reveals it’s simply Java serialization:


private String encodedString(Object objectToWrite) throws IOException {
    byte[] data = SerializationUtils.serialize(objectToWrite);
    return new Base64().encodeAsString(stream.toByteArray());
}

Now we can manufacture any test scenario we want, and better yet, we can keep the test data setup scoped within each test without any fear of that data interfering with other tests.

Check it out:

Github repository

Heroku site

Written by Brandon Byars

February 10, 2014 at 5:00 am

Multi-protocol remote stubbing with mountebank

leave a comment »

mountebank is a pet project I’ve been working on for a while now to provide multi-protocol and multi-language stubbing.  It does HTTP and HTTPS stubbing, but HTTP and HTTPS stubbing is a solved problem.

What mountebank does that’s really cool is stubbing and mocking for other protocols.  TCP stubbing is way cool because I’m not aware of an equivalent tool currently available.  We spiked this out at my current client to stub an RMI-like protocol.  The test code created the Java object that it wanted the remote server to return, serialized it, and relied on mountebank to do the right thing at the right time.

It also supports SMTP, at least for mock verification.  That way your app under test can connect to a real SMTP server, but that server won’t send any actual email.  It will, however, allow your test to verify the data sent.

This first release of mountebank ships with:

  • over the wire mock verification for http, https, tcp, and smtp
  • over the wire stubbing for http, https, and tcp
  • advanced stubbing predicates and multi-response stubbing for stateful services
  • record and replay proxying for http, https, and tcp
  • javascript injection when the tool doesn’t do what you need

Feedback welcome! 

heroku site: http://www.mbtest.org/

github: https://github.com/bbyars/mountebank

Written by Brandon Byars

February 3, 2014 at 8:25 pm

Posted in Uncategorized