A Guide to automated testing for microservices
Tags: testing, microservices, automation, guide
Published: 2020-04-22 08:47:01

Today a lot of applications use microservice architecture, but what should we know and how we should test this type of software? In this article, we will consider basic theoretical concepts about CDC testing and testing tools that will be helpful.

Microservices architecture - is a collection of small services and each service responsible for certain functionality and can be written by using different programming languages, approaches, and solutions. One of the main goals of microservices-based application it's define strong contracts between the various microservices.

Consumer-driven contract testing (CDC) - is a technique that allows us to test each application in isolation. After executing consumer tests we get generated pact files that will be used for getting verification results during executing provider tests. As a result, we will have artifacts that will be placed in Pact-broker.

Let’s familiarize ourselves with the main concepts from the official sources.
[Pact](https://docs.pact.io/) - is a code-first consumer-driven contract testing tool, and is generally used by developers and testers who code. In general, a contract is between a consumer (for example, a client that wants to receive some data) and a provider (for example, an API on a server that provides the data the client needs).
[Pact Broker](https://github.com/pact-foundation/pact_broker) - is an application for sharing for consumer driven contracts and verification results. It is optimized for use with "pacts" (contracts created by the Pact framework), but can be used for any type of contract that can be serialized to JSON. The Pact Broker is an open source tool that requires you to deploy, administer and host it yourself but also they provide plug-and-play option at [pactflow.io](https://pactflow.io/). 
[Mocha](https://mochajs.org/) - is a feature-rich JavaScript test framework running on [Node.js](https://nodejs.org/) and in the browser, making asynchronous testing simple and fun. Mocha allows you to use any assertion library.
[Chai](https://www.chaijs.com/) - is a BDD / TDD assertion library for node and the browser that can be delightfully paired with any javascript testing framework.
[Axios](https://github.com/axios/axios) - promise based HTTP client for the browser and node.js.

## Simple example of CDC testing.
Here is a code snippet from package.json where you can see all packages that will be used.
```javascript
{
"@pact-foundation/pact": "^9.9.3",
"axios": "^0.19.0",
"chai": "^3.5.0",
"mocha": "^5.1.1"
}
```

### Step 1. Consumer tests. 
There are two different specs that will be describing TODO API - “[JSONPlaceholder](https://jsonplaceholder.typicode.com/)” and USER API - “[REQ | RES](https://reqres.in/)” . The first example you can see below. Second spec the same except the request and response objects.

todoClient.js
```javascript

const getUserById = (id, baseURL) => {
   return axios.request({
       method: "GET",
       baseURL,
       url: `/users/${id}`,
       headers: { Accept: "application/json" },
   })
};
```
todo.spec.js
```javascript

describe("The Todo API", () => {
 const mockServerURL = 'http://localhost:1234';

 // Create the Pact object to represent your provider
 const provider = new Pact({
   port: 1234,
   log: path.resolve(process.cwd(), "logs", "mockserver-integration.log"),
   dir: path.resolve(process.cwd(), "pacts"),
   consumer: "Consumer",
   provider: "Todo Provider",
   spec: 2,
   logLevel: "error",
   pactfileWriteMode: "merge"
 });

 // this is the response you expect from your Provider
 const EXPECTED_BODY = {
   userId: 1,
   id: 1,
   title: 'delectus aut autem',
   completed: false
 };

 // Start the mock server
 before(() => provider.setup());

 // Write Pact when all tests done
 after(() => provider.finalize());

 // verify with Pact, and reset expectations
 afterEach(() => provider.verify());

 describe("Get todo by id", () => {
   before(async () => {
     const interaction = {
       state: "Get todo",
       uponReceiving: "A request for one todo",
       withRequest: {
         method: "GET",
         path: "/todos/1",
         headers: {
           Accept: "application/json",
         },
       },
       willRespondWith: {
         status: 200,
         headers: {
           "Content-Type": "application/json; charset=utf-8",
         },
         body: EXPECTED_BODY,
       },
     };
     // add interactions to the Mock Server
     await provider.addInteraction(interaction)
   });

   it("GET /todos/1", async () => {
     let { data } = await getTodoById(1, mockServerURL);
     expect(data).to.eql(EXPECTED_BODY);
   });
 })
});
```
After running tests we will receive the following pact files:
consumer-user_provider.json
consumer-todo_provider.json:
```javascript
{
 "consumer": {
   "name": "Consumer"
 },
 "provider": {
   "name": "Todo Provider"
 },
 "interactions": [
   {
     "description": "A request for one todo",
     "providerState": "Get todo",
     "request": {
       "method": "GET",
       "path": "/todos/1",
       "headers": {
         "Accept": "application/json"
       }
     },
     "response": {
       "status": 200,
       "headers": {
         "Content-Type": "application/json; charset=utf-8"
       },
       "body": {
         "userId": 1,
         "id": 1,
         "title": "delectus aut autem",
         "completed": false
       }
     }
   }
 ],
 "metadata": {
   "pactSpecification": {
     "version": "2.0.0"
   }
 }
}
```
### Step 2. Publishing pact files. In this example we will use [pactflow.io](https://pactflow.io/) (Developer FREE plan). 

publish.pacts.js
```javascript

const gitHash = require('child_process')
    .execSync('git rev-parse --short HEAD')
    .toString().trim();

const opts = {
   pactFilesOrDirs: ['./pacts/'],
   pactBroker: 'https://yourCompanyName.pact.dius.com.au',
   pactBrokerToken: 'yourPactBrokerToken',
   tags: ['prod', 'test'],
   consumerVersion: gitHash
};

pact
   .publishPacts(opts)
   .then(() => {
       console.log('Pact contract publishing complete!');
    })
   .catch(e => {
       console.log('Pact contract publishing failed: ', e)
   });

```

For getting pactBrokerToken you need to proceed to /settings/api-tokens and copy Read/write token (CI)
![proceed to /settings/api-tokens and copy Read/write token (CI)](https://dev-to-uploads.s3.amazonaws.com/i/mggbcc54rupa1cuhpg0x.png)



After running this script we will receive the following results:
![the following results](https://dev-to-uploads.s3.amazonaws.com/i/lyzu5lkt3cnhzdunbqaj.png)
![the following results](https://dev-to-uploads.s3.amazonaws.com/i/kf9670q2nm2chjs5hvp3.png)
### Step 3. Pacts Verification
```javascript

provider.spec.js
const opts = {
   logLevel: "INFO",
   providerVersion: "1.0.0",
   pactBrokerUrl: 'https://yourCompanyName.pact.dius.com.au',
   pactBrokerToken: 'yourPactBrokerToken',
   publishVerificationResult: true
};

describe("Pacts Verification", () => {
   it("Validates the expectations of Todo provider", () => {
       return new Verifier({
           ...opts,
           provider: 'Todo Provider',
           providerBaseUrl: 'https://jsonplaceholder.typicode.com',
       }).verifyProvider();
   });

   it('Validates the expectations of User provider ', () => {
       return new Verifier({
           ...opts,
           provider: 'User Provider',
           providerBaseUrl: 'https://reqres.in/api',
       }).verifyProvider();
   });
});
```
After running tests we will receive the following results:
![the following results](https://dev-to-uploads.s3.amazonaws.com/i/2osk0oonkdddocerklek.png)
VIEW PACT:
![VIEW PACT](https://dev-to-uploads.s3.amazonaws.com/i/l6jdueqfsdnptejnxvrv.png)
![VIEW PACT](https://dev-to-uploads.s3.amazonaws.com/i/wcottyf6ymutf2rkyn1f.png)
Let’s change the title in the expected result **from step 1** for simulating an error.

Previous object:
```javascript

 const EXPECTED_BODY = {
   userId: 1,
   id: 1,
   title: 'delectus aut autem',
   completed: false
 };
```
New object:
```javascript

 const EXPECTED_BODY = {
   userId: 1,
   id: 1,
   title: 'Our new title that will trigger the error',
   completed: false
 };
```
Result after running tests:
![Result after running tests](https://dev-to-uploads.s3.amazonaws.com/i/4i86mnoz7efown60jvgg.png)


Console output:
![Console output](https://dev-to-uploads.s3.amazonaws.com/i/874q3drfw20lonmac79k.png)

## Conclusions.
   Testing microservices it's a very difficult process that requires a good knowledge of different testing tools for achieving needed goals. In this article, we considered basic concepts and simple example of writing CDC tests using Pact. You can deeply familiarize yourself with this powerful testing tool on the official site and use all provided features for testing your microservices.