Isolate and Test

Diving into a new technology, language, tool or architecture is always fun. At least for the first day or so of playing around with it.

Fairly soon, you and your fellow pioneers will decide on building this amazing new thing with this even more awesome new technology that will provide the business with so many cool features that you really think a plaque with your name at the entrance of the office is the minimum they can do to thank you.

Then a more down-to-earth colleague comes and asks the following questions:

  • How are you going to deploy it?
  • How are you going to test it?
  • How are you going to document it?
  • How are you going to integrate it with the rest of the systems?
  • Etc.

She is right, don’t get me wrong. However, I still want the plaque at the entrance of the office. That leaves us with only one option: let’s solve those issues.

Today’s blog post is about SOA (Service Oriented Architecture), which is the architecture we are excited about here in NAP, and the question we are going to address is “How are you going to test it?“.

This is how a typical SOA scenario might look:

testing-soa-all-services

In order for Service A to fulfil a request, it has to query all its dependencies: Services B, C & D. Furthermore, Service C is a consumer of Service D, so that coupling has to work properly as well. If we’d like to run a test on Service A, the following would be a brief checklist of what we’ll need from the testing environment:

  • All services have to be up.
  • All services’ databases have to be up.
  • All services that Service A depends on should be running the version our test is assuming they are.
  • All services’ databases should contain the right schema for the version deployed.
  • All services’ databases contain the data we rely on for our test to run successfully.

Let’s be pragmatic: in a fast-changing development environment, that’s not going to happen; there are just too many things that can go wrong and cause our automated tests to fail.

Testing the service in isolation

If every service is tested to behave the way we expect it to, as long as the contracts between services are kept, the whole system should work fine when we tie the services together.

We recognise this is a very idealistic approach and ignores subtle errors that pop up when testing a fully integrated service. However, today’s particular objective is to build confidence in a particular service whilst minimising the impact of external dependencies; it is a part of our test strategy, but certainly not all of it. There is still value in both system testing and integration testing, it’s just that those build on top of isolated service testing.

To take this into practice, we do the following: mock everything but the service we want to test.

testing-soa-services-mocked

As you can see on the diagram, there is no dependency between C and D. Our Service A will hit C’s final output mock regardless of how it was generated.

Here’s our set of requirements:

  • We want our mocks to be proper HTTP servers so Service A can go through the whole stack (Marshalling -> HTTP request -> Network -> HTTP response -> Un-marshalling) while accessing the services it depends on.
  • We want to configure our mocks on a per-test basis.
  • We want to bind our mock servers dynamically to free ports on the test server so, no matter how many tests are running concurrently, we never get an unable to bind error.
  • We want to configure our service to hit our mocks and bootstrap in an embedded container.
  • Once everything is up, we want to hit the service we are testing and run assertions on the responses we get from it.
  • We want everything to run on a single JVM and be easily pluggable into our CI server.
  • We want to make the writing of a test as easy as possible, so our testers can do their job and not deal with setting up environments.

This list is screaming for one clear goal: a testing framework! And so we built one.

We took some very handy tools as Restito, Jetty, REST-assured, TestNG and we glued them together with some Spring in order to get a framework that does the following for us:

testing-soa-timeline

The important thing for us is that writing tests is now a simple, quick and concise task. A test using this approach looks something like the following snippet:

@ContextConfiguration("classpath:somewhere/local-config.xml")
public class SomeServiceTest extends AbstractTestNGSpringContextTests {

    @Autowired
    private ServiceClient serviceClient;

    @Test
    public void testDoThatThing() {

        ResponseSpecification responseSpec = new ResponseSpecBuilder()
                .expectBody("total", equalTo(123))
                .expectBody("data[0].createdBy", equalTo("456"))
                .build();

        serviceClient.doThatThing(responseSpec);
    }
}

(We used the IntelliJ IDEA plugin Copy as HTML to format the code.)

The code implements the workflow above as follows:

  • Our test class extends from AbstractTestNGSpringContextTests which is the Spring class that let us hook Spring into TestNG.
  • We configure the Spring context by annotating our test class. The xml file contains all the information about the mocks that we are going to use in order to run this test and the service we are going to test.
  • Upon construction of the ApplicationContext, mock servers are started, the service is configured to hit them and started in an embedded container.
  • A client to the service we are going to test is injected by Spring. The client is configured to hit the service on the port the embedded container started it.
  • The class’ test methods are run. ResponseSpecification is a REST-assured class that lets us define all our assertions in the very expressive Hamcrest notation. The test methods consist of basically defining what we expect it to happen when we hit our services with a specific call.
  • After all the tests are run, the ApplicationContext is destroyed and all the services that were started in order to run the test are stopped.

Although there is a lot more to do, I feel I’m a little closer to that plaque by the office entrance already.

2 thoughts on “Isolate and Test

  1. I really liked your post. This is definitely area I would love to explore further. I’ve used most of the technologies you mentioned, but your setup sounds very mature and repeatable than my attempts in the past. Is there you could publish a working prototype, some kind of skeleton project with mocked dependencies on github or such?

    Thank you for the post.

    Piotr

Leave a Reply