Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Writing End to End Tests for a Microservices Architecture

DZone's Guide to

Writing End to End Tests for a Microservices Architecture

Despite being complex, writing end-to-end tests for microservices is extremely important, and service virtualization is key to making it work.

· Microservices Zone ·
Free Resource

Learn how modern cloud architectures use of microservices has many advantages and enables developers to deliver business software in a CI/CD way.

One of the main aspects of microservices architecture is that the application is formed as a collection of loosely coupled services each one deployable independently and communicated each other with some kind of light protocol.

Now suppose that you want to write an end to end test for Cart Service. You will quickly see that it is not easy at all, let me enumerate some of the reasons:

  • Cart Service needs to know how to boot up Pricing Service, Catalog Service, and MongoDB (and if you want to involve the front-end as well then Coolstore GW and WebUI).
  • Cart Service needs to prepare some data (fixtures) for both of external services.
  • You communicate with services using a network. It might occur that some tests fail not because of a real failure but because of an infrastructure problem or because the other services have any bug. So the probability of these tests become flaky and start failing not because any changed introduced in current service is higher.
  • In more complex cases running these tests might be expensive, in terms of cost (deploying to the cloud), time (booting up all the infrastructure and services) and maintenance time.
  • Difficult to run them in developer machine, since you need all the pieces installed on the machine.

For this reason, end to end tests are not the best approach for testing a microservice, but you still need a way to test from the beginning to the end of the service.

It is necessary to find a way to "simulate" these external dependencies without having to inject any mock object. What we need to do is cheat the service under test so it really thinks it is communicating with the real external services, when in reality it is not.

The method that allows us to do it is Service Virtualiztion. Service virtualization is a method to emulate the behavior of component applications such as API based.

You can think about service virtualization as mocking approach you used to implement in OOP but instead of simulating at the object level, you simulate at the service level. It is mocking for the enterprise.

There are a lot of service virtualization tools out there, but in my experience, in the JVM ecosystem, one of the tools that work better is Hoverfly.

Let's see how an "end-to-end" test looks like for Cart Service.

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
    properties = "CATALOG_ENDPOINT=catalog")
public class CartServiceBoundaryTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @ClassRule
    public static HoverflyRule hoverflyRule = HoverflyRule.inSimulationMode(dsl(
        service("catalog")
            .get("/api/products")
            .willReturn(success(json(ProductsObjectMother.createVehicleProducts())))
    ));

    @Test
    public void should_add_item_to_shopping_cart() {

        final ShoppingCart shoppingCart = this.restTemplate.postForObject("/api/cart/1/1111/2", "", ShoppingCart.class);

        assertThat(shoppingCart)
            .returns(0.0, ShoppingCart::getCartItemPromoSavings)
            .returns(2000.0, ShoppingCart::getCartItemTotal)
            .returns(-10.99, ShoppingCart::getShippingPromoSavings)
            .returns(2000.0, ShoppingCart::getCartTotal)
            .extracting(ShoppingCart::getShoppingCartItemList)
            .hasSize(1);
    }
}


This service is implemented using Spring Boot, so we are using Spring Boot Test framework. The important part here is that the URL where Catalog service is deployed is specified by using CATALOG_ENDPOINT property. And for this test, it is set to catalog.

The next important point is the Hoverfly class rule section. In that rule, the following things are specified:

  1. An HTTP proxy is started before the test and all outgoing traffic from JVM is redirected to that proxy.
  2. It records that when a request to host catalog is done and the path is /api/products it must return a success result with given json document.

The test itself just uses TestRestTemplate (it is a rest client) and validates that you can add some elements to the cart.

Notice that you don't need to configure where the HTTP proxy is started or configure any port because Hoverfly automatically configures JVM network parameters so any network communication goes through Hoverfly proxy.

So notice that now you don't need to know how to boot up Catalog service nor how to configure it with correct data.

You are testing the whole service within its boundaries, from incoming messages to outgoing messages to other services, without mocking any internal element.

You may be wondering "What happens if a current service has also a dependency on a database server?"

In this case, you do nothing since the service itself knows which database server is using and the kind of data it requires, you only need to boot up the database server, populate required data (fixtures) and execute tests. For this scenario, I suggest you using Arquillian Cube Docker to bootup database service from a Docker container so you don't need to install it on each machine you need to run tests and Arquillian Persistence Extension for maintaining the database into a known state.

In the next example of rating service, you can see briefly how to use them for persistence tests:

public class ApueCubeRatingServiceTest {
  // Starts in local dockerhost (docker machine or native) mongo docker image before running the test class
  @ClassRule
  public static ContainerDslRule mongodbContainer = new ContainerDslRule("mongo:3.2.18-jessie")
      .withPortBinding(27017);

  //Defines APE (Arquillian Persistence Extension to work as rule)
  @Rule
  public ArquillianPersistenceRule arquillianPersistenceRule = new ArquillianPersistenceRule();

  // Defines to use MongoDb as NoSql Populator
  @MongoDb
  @ArquillianResource
  NoSqlPopulator populator;

  @Test
  public void should_calculate_average_rating_when_adding_an_already_inserted_item() {

    createPopulatorConfiguration()
                .usingDataSet("single_rating_with_double.json")
                .execute();

    // Execute test

  }

  @After
  public void tearDown() {
    createPopulatorConfiguration().clean();
  }

  private NoSqlPopulatorConfigurator createPopulatorConfiguration() {
          return populator.forServer(
              mongodbContainer.getIpAddress(),
              mongodbContainer.getBindPort(27017))
              .withStorage(TEST_DATABASE);
  }
}


With this approach, you are ensuring that all inner components of the service work together as expected and avoiding the flakiness nature of end to end tests in microservices.

So end to end tests in any microservice is not exactly the same as an end to end test in a monolith application; you are still testing the whole service, but keeping a controlled environment, where the test only depends on components within the boundary of service.

How do contract tests fit in? Well, everything shown here can be used in consumer and provider side of contract testing to avoid having to boot up any external service. In this way, as many authors conclude, if you are using contract tests, these become the new end to end tests.

Discover how to deploy pre-built sample microservices OR create simple microservices from scratch.

Topics:
microservices ,mongo db ,service virtualization ,spring boot ,software testing

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}