Janis Köhr & Antoniya Atanasova
27. July 2018
8 min

Modern Microservices Testing with TypeScript in Node.js on Microsoft Azure: A Real World Example

This is the third post of a blog series regarding ‘Microservices with Node.js, TypeScript and Microsoft Azure’. In this series we will keep you up with different parts of this topic.

In recent times most of the good practices regarding testing microservices cover examples that are based on Spring Boot. As much as a cool technology it is, another growing alternative is Node.js. Therefore, in this blog post we want to share our experience on integrating all of the good and modern microservices testing practices in a Node.js world using TypeScript and running on Microsoft Azure.

Overview of used tools

First of all a small overview of the major tools we have used in our real world example:

  • Mocha – A famous JavaScript test framework
  • Sinon.js – Provides test spies, stubs and mocking for JavaScript
  • Chai – An assertion library for BDD and TDD style assertions
  • Chai-as-promised – A plugin for Chai, that provides assertions for Promises
  • Chai-http – Another Chai plugin, that is used for making http calls.

Short disclaimer: We will not give a detailed explanation of how to use those tools. We will just use them and explain by example how we test the different layers of an application. If you need more insides about Mocha, Sinon or Chai and how to use them, you can find many blog posts and tutorials already out there in the internet.

Introduction into our showcase application

To be able to describe all the different test types that we typically use during development, we had the idea to setup a small real world example showcase. This showcase provides just enough logic to be able to explain the different testing strategies. It should not be complete in any way. It just shows how we test our code when it comes to different kind of scenarios. In the following sketch our real world showcase is displayed.

Real world example sketch

Basically the showcase consists of a REST microservice that handles vehicles – the VehicleService. It runs on Microsoft Azure and stores its entities in Azure Cosmos DB. It’s possible to create and read the vehicles. Moreover, it has the special functionality, that when asked for one specific vehicle id, it gives more detailed information about the damaged status of the vehicle as well. This detail it retrieves from a third party service – the DamageService. In our showcase we did not implement the DamageService and in a real world application this most likely would be a service provided by another team or even another organisation.

Testing overview

For structuring our tests, we have been guided by the concepts in the Agile Testing Pyramid. As also mentioned in Practical Test Pyramid the needs for a microservices application testing have changed and currently you can also add contract tests, for example. In the following sections we have tried to list the different testing strategies that we consider to be important and to explain them using the given example. To do so, we use the experience from a current project and try to make the illustrations as concise as possible.

Unit Testing

In our unit testing approach we are more of a solitary type. This means that we completely isolate external dependencies by stubbing them. For our unit layer we use Mocha, Sinon and Chai. Furthermore, for writing the tests in async/await style, we are using an async function and therefore no done() callback has to be used in the it block.

In the preceding example we see a code snippet of the unit tests of the VehilceService. Like already mentioned, we use stubs for all external parts and just test the unit’s logic itself. Therefore, we define the stubs’ behavior and afterwards call the method we want to test. At the end we expect that the result equals the expected object or format. We write unit tests for each class and every public method we have in our code. It improves stability and code quality. Furthermore, it supports us in structuring our code into handy parts.

Integration Testing

As many teams we have also struggled with clarifying where the boundaries between component and integration tests are. After some team discussion and research, we have come to a solution that fits for us. That is actually the goal for every team – not to ignore integration testing because it is unclear or too complex. Rather, try to find your way by combining best practices in the specific context applied. The idea behind our classification is that when there are more than two components participating, then we have component tests. If it is, for example, only our microservice and the database – then it is an integration test. You are going to see the idea more refined in our showcase.

There we have two main integration test examples. The first one focuses on the relation between our VehicleService and the third party DamageService. The second one focuses on the relation between our service and the database.

One difference in comparison to the unit tests set up is that Sinon Stubs are not used any more. We do however have some simulations, but on a different level. One level is the simulation of the DamageService and the other one is the connection to a test database. Normally, you would like to use an in memory database solution, in our case with Azure Cosmos DB that due to technical constraints is not possible. There is a docker emulator which unfortunately comes with some restrictions (Windows 10, Windows Containers etc.) and therefore cannot be used.

The previous example shows a simple integration test. We just call the method with a valid input and the simulation is responding accordingly. The result is asserted and no Stubs are used at all, as you can see. It is also worth mentioning our global integration tests set up: Before all int tests we create an instance of the Test Simulation server and start it, afterwards we create the database. After each test the test data is cleaned from the database and at the very end we shut down the Test Simulation server and delete the database.

Some additional thoughts on integration testing of external services:

Our first choice here would be doing Consumer-Driven Contracs. However, like many organizations, we are having issues in convincing provider teams in doing contract testing. Until then, we want still to have tests that do not call our real providers every time. Therefore, we have used service simulators for simulating our Providers’ behavior.

HOW? In Java world, frameworks like Wiremock and Hoverfly are quite well adopted. Unfortunately, we have not found so well established tools in JavaScript world that meets our needs. An often used solution in the community is JSON Server. But due to our enterprise context, the APIs that are consumed are not always designed the way JSON Server exposes the data by default. Another alternative was Nock framework which was also discarded, because it does override the internal Node.js functions which is against our integration tests vision.

The solution – a custom implementation of an Express server where the routes with the expected behavior are installed.

Component Testing

Looking back into our showcase, where we have our microservice and two more external components that we use (DamageService and Database), then we have fulfilled preconditions for component testing. In this setup we can cover a small sub system of our application. What should not be done here is to test all possible cases and error scenarios again, that have been unit or integration tested already. It’s kind of the art to figure out, what makes sense to test for in the specific situation and combination of the given components.

In our example (VehicleServiceComponent.spec.ts), you can have a look at our component test implementation. Basically, we are testing the combination or interaction scenarios of the Components like Database, DamageService and VehicleService – that’s it.

Endpoint Testing

Endpoint tests or others call them REST API End-to-end tests focus on testing the application’s controllers. Instead of starting the service and sending some requests via Postman for example, we have used chai-http for testing this exact behavior. In the example below, we can see an endpoint test implementation. The support of chai-http gives us really easy to read and understand tests. At the end you need to verify the status code and the body returned. In error cases, it is also good to verify the error message (when available), not only the status code.

Contract Testing

We have already explained our current issues for not actively using Consumer-Driven Contracts. Nevertheless, we have implemented them and provide an example of how you can use the PACT framework in Node.js context. The generated contract can be found under /contract/pacts.

This tests can completely replace the need of DamageService integration test. We are not going to go into details on what Consumer-Driven Contracts are, therefore for getting a basic understanding, please refer to another blogpost: Introduction to Microservices Testing and Consumer Driven Contract Testing with PACT.

Automate Everything

Before we come to our final conclusion, we also want to give a brief overview of our project’s test automation:

At the very end everything is fully integrated into a scripted Jenkins build pipeline and after everything is green, deployed automatically to Azure.  First the unit tests are being executed, followed by the integration and component tests. Then the contract tests are being executed and the results are pushed to the PACT broker.

After the application has been deployed on one of the slots in Azure, we are running a small suite of endpoint tests, which we tend also to name “after deployment checks”. In there we are sending requests to the running microservice and verifying responses.  As we mentioned, purpose of those tests is not to verify everything of your business logic and make many calls, rather an after deployment check that the service is available and running in the cloud.

Summary

If you are looking for software quality in a microservice world, it is essential to use tests in different granularity and at different levels of your system. Tools such as Mocha, Sinon and Chai as well as a strong test automation support you as a developer to keep up with the fast development cycles. Tests can not only help to detect bugs and logic problems, but are also suitable for early identification of design flaws and problems in structuring your modules.

In our example code base we therefore show you which parts of your service should be tested and how, so that deployment in the cloud goes much smoother. In conclusion, the following picture shows once again which testing strategies we have implemented to create a reliable application.

Real world testing overview

Github repository

For the full example can be seen here:

https://github.com/nt-ca-aqe/blog-microservices-testing-nodejs-typescript

Comment article