Integration Testing of an Asynchronous Node.js Microservice

nodejs logo

This article will explore one way to test the asynchronous execution of code in a Node.js application.

Let’s say we have code that looked like the following:

'use strict';

const express = require('express');

const app = express();

app.post('/', function (req, res) {

    // PARSE AND PROCESS POST BODY     #A

    asyncService.execute(data, function(err, results) {     #B

        if (err) {
            logger.error('Error: error executing async service');
        }; 

        // PROCESS RESULTS     #C

        logger.info('Success: successful execution of async service + results');

    });

    res.status(200).send('Successful post!');     #D
});

const server = app.listen(8081, function () {
    const host = server.address().address;
    const port = server.address().port;
   
    console.log("Example app listening at http://%s:%s", host, port);
});

module.exports = server;

We could say the application is doing 4 main things:

A. Accepting a HTTP Post request + BUSINESS LOGIC.
B. Making a call to some asynchronous service.
C. BUSINESS LOGIC.
D. Returning a response to the client.

The important thing to note however, is that these four items do not occur sequentially. Instead, they occur in the following order:

A —–> B —–> D —–> C

Therefore, we will be returning a response to the client prior to obtaining results from the asynchronous service. So, how can we run an integration test that will capture the business logic performed on the results of the asynchronous service?

Since we cannot analyze the server response, we must somehow devise a test that checks the results from the asynchronous service and does so at the appropriate time.

First, let’s create a file called test.js.

Install Mocha as a development dependency:

npm install mocha —save-dev

Additionally, install the following dependencies:

sinon
chai

Now, copy and paste the following snippet into test.js.

const sinon = require('sinon');
const chai = require('Chai');
const assert = chai.assert;
const request = require('request');
const logger = require('winston');

const server = require('./app');
describe('All Asynchronous Node.js Microservice tests...', function () {
    var info;

    beforeEach(function() {
        info = sinon.stub(logger, 'info');     #E
    });

    afterEach(function () {
        logger.info.restore();
    });

    after(function () {
        server.close();
    });

    it('Succes: Correct message output', function (done) {
        this.timeout(10000);

        const body = ''; // FETCH BODY OF HTTP REQUEST

        var options = {
            method: 'post',
            url: 'http://localhost:8081',
            headers: {
                'Content-Type': 'text/plain'
            },
            body: body
        };
        request(options, function (err, res, body){     #F

            assert(res.statusCode, 200);

            setTimeout( function () {     #G
                assert(info.calledOnce);
                sinon.assert.calledWith(info, sinon.match(/Success:/));     #H
                done();
            }, 5000);
            
        });
    });

});

Referring back to the original server code, note that we logged the results of the asynchronous service. Logging will serve as our main strategy for tracing the asynchronously executed code. In order to check these results we use a library called sinon to stub the info method of our logger module (STEP E). This will then allow us to analyze the arguments passed to the method when invoked (STEP H). You will notice that in (STEP F) we send a request to our server. In order to check the results at the appropriate time we include a set timeout function in the callback function passed to the request method. Inside of the set timeout function, we use a regular expression to test the arguments passed to our logger.

With this strategy, we can then create multiple tests with different post bodies, to ensure the application / business logic is behaving properly for all inputs.