Add Customer API – CustomerMiddleware – Add Customer Unit Tests – Node.js API with TDD Tutorial

In the last post, we have seen how to write unit tests and functional code for the the CustomerService component’s createCustomer() function. Let’s continue with the next component in the layers, Middleware, now.

Customer Middleware – Add Customer Unit Tests

In this post, we will start with writing test scripts and coding the required functional script for the CustomerMiddleware module file for Add Customer functionality.

CustomerMiddleware Spec File Preparation

Let’s set up the test file for the customer.middleware.js file now. As per the low-level design for the Add Customer functionality, the middleware function addCustomer() will be calling the service function createCustomer() with the customer object as input. Then the calling function handles the promise returned from the called function. 

1. Load Required Test Modules

We need to prepare the spec file to add the test suites for the testing the behavior mentioned above. Just like previous test files, let’s start with the initial test modules loading. 

'use strict';

var chai = require('chai');
var expect = chai.expect;
var sinon = require('sinon');

Since the middleware function will be dealing with the HTTP request and response objects, we will have to mock these objects as they are external dependencies to the middleware functions.

There is a module called node-mocks-http which can be used for mocking these HTTP objects for testing the router or middleware functions. Install this module before we use it by running the below statement on the command line, and this package will be added as a development dependency.

npm install node-mocks-http --save-dev

Once the package has been downloaded, add the below line in the customer.middleware.spec.js file. This statement will load the node-mocks-http module to the httpMocks variable which will be used to create mockups for the request and response objects in the upcoming specs.

var httpMocks = require('node-mocks-http');

As the createCustomer() function of the CustomerService is also returning a promise, which came from the CustomerModel’s create function, we need to either mock or stub the promise for testing the CustomerMiddleware’s addCustomer() function.

We will be using the stubbing approach in this test file. So we need to load the promise module from the bluebird package to use it in the specs.

var bluebird = require('bluebird');
var Promise = bluebird.Promise;

2. Load Component Files

Next variables to load are the javascript file under test and the other javascript files which are dependencies of the file being tested. Here we are going to test the CustomerMiddleware and CustomerService is the dependency of the former module. Both the modules are loaded via the customer.module.js file as below.

var CustomerModule = require('../../../modules/customer/customer.module')();
var CustomerMiddleware = CustomerModule.CustomerMiddleware;
var CustomerService = CustomerModule.CustomerService;

3. Load Fixtures

Let’s load the fixtures that are needed for testing both successful and failure paths of the addCustomer() method. The customer and error fixtures are loaded from the fixtures.js file as below.

var Fixtures = require('../../fixtures/fixtures');
var CustomerFixture = Fixtures.CustomerFixture;
var ErrorFixture = Fixtures.ErrorFixture;

Finally, let’s create the global variables for request and response objects and the next function as well.

var req, res, next;

4. Prepare Main Test Suite

Now, let’s create the main test suite and add the fixtures setup needed before any test spec runs. As this test suites will require request and response objects and next function, we need to create the mocks for these dependencies before each test spec runs. Below code block will take care of this mockup setup activity.

describe('CustomerMiddleware', function () {

    beforeEach(function () {
        req = httpMocks.createRequest();
        res = httpMocks.createResponse();
        next = sinon.spy();
    });
    
});

We are using the node-mocks-http module to create the mocks for both HTTP request and response objects. For testing the middleware function’s behavior, invoking the next() function in the stack to pass the execution control to, we can create a spy as the next function.

This spy function can be used to verify that it was successfully called after adding the customer to the backend database through the service method.

Unit Test 13: AddCustomer Success Test Case

This test is to verify the successful addition of new customer in the MongoDB collection via the middleware function. 

Test Suite

Let’s create the new test suite for addCustomer() function with few required variables declaration as below.

describe('addCustomer', function () {
    var createCustomer, createCustomerPromise, expectedCreatedCustomer, expectedError;

    beforeEach(function () {
        createCustomer = sinon.stub(CustomerService, 'createCustomer');
        req.body = CustomerFixture.newCustomer;
    });

    afterEach(function () {
        createCustomer.restore();
    });

});

Just like the previous test suites, we need to set up the fixtures before the test spec is written. The beforeEach() and afterEach(), mentioned as above, are performing those tasks.

As we discussed earlier, we are creating a stub method, createCustomer(), for the CustomerService and also the newCustomer fixtures is assigned to the request’s body object as it will be parsed inside the middleware function to pass it onto the service to create the customer. 

Test Script

Next, we will create the spec for testing the addCustomer() method of the CustomerMiddleware. Inside this spec, at first, expected output and the expected behavior of the dependent function createCustomer() is defined. So when we invoke the addCustomer() function, which is under test, the stubbed function will behave as it’s defined in the spec with the expected input and output. 

it('should successfully create new customer', function () {
    expectedCreatedCustomer = CustomerFixture.createdCustomer;

    createCustomerPromise = Promise.resolve(expectedCreatedCustomer);
    createCustomer.withArgs(req.body).returns(createCustomerPromise);

    CustomerMiddleware.addCustomer(req, res, next);

    sinon.assert.callCount(createCustomer, 1);

    return createCustomerPromise.then(function () {
        expect(req.response).to.be.a('object');
        expect(req.response).to.deep.equal(expectedCreatedCustomer);
        sinon.assert.callCount(next, 1);
    });

});

Let’s break this code block line by line and see what each of those lines does. Here, at the first line of it() block,  we are assigning the variable expectedCreatedCustomer with the fixture data. 

expectedCreatedCustomer = CustomerFixture.createdCustomer;

The statement is defining the behavior of the Promise function to resolve with the expected output when it’s successfully complemented the operation.

createCustomerPromise = Promise.resolve(expectedCreatedCustomer);

At the third line, we are defining the behavior of the dependent method, which should be called from inside the CustomerMiddleware’s addCustomer() method. The stubbed function is called with the request’s body object as input and returns the successful promise function with the expected output data, the expectedCreatedCustomer.  

createCustomer.withArgs(req.body).returns(createCustomerPromise);

So far, the behavior and expectation of the spec are set up, and the next statement is invoking the function under test to verify it’s behavior.

CustomerMiddleware.addCustomer(req, res, next);

The rest of the statements in it() block are verifying or asserting the addCustomer() function’s behavior as expected or defined already. The below line asserts that the createCustomer() function is invoked once.

sinon.assert.callCount(createCustomer, 1);

Last code block asserts that the middleware function under test is handling the promise function returned by the stubbed service function. Also, the output returned by the successful promise function is checked against the expected output.

A response attribute is an object, which is supposed to be set in the request object by the middleware function and is of the same structure and content as the expectedCreatedCustomer

return createCustomerPromise.then(function () {
    expect(req.response).to.be.a('object');
    expect(req.response).to.deep.equal(expectedCreatedCustomer);
    sinon.assert.callCount(next, 1);
});

Finally, the next() function invocation is also verified with Sinon’s assert API. As usual, when we run the test suites once again will show one failing spec as shown below.

Unit Test 13: CustomerMiddleware - addCustomer() Success Test Case - Failed 1
Unit Test 13: CustomerMiddleware – addCustomer() Success Test Case – Failed 1

Code

As the next logical step, we will have to write the functional code to make the failing test to pass. Let’s start with adding an empty function addCustomer() in the customer.middleware.js file.

(function () {
    'use strict';

    module.exports = {
        addCustomer: addCustomer
    };

    function addCustomer(req, res, next) {}

})();

After adding the above code and running the test suites will show that we resolved the above mentioned ‘CustomerMiddleware.addCustomer is not a function‘ error. However, it will also throw the below error as we haven’t completed the expected behavior of the addCustomer() function yet. 

Unit Test 13: CustomerMiddleware - addCustomer() Success Test Case - Failed 2
Unit Test 13: CustomerMiddleware – addCustomer() Success Test Case – Failed 2

As the expected behavior is written in the spec file and if we keep writing the functional code to satisfy the failing behavior, the operational code will be written entirely. So, let’s continue with code which will resolve the error mentioned above.

Since the latest error shows that createCustomer() method is not called once, we will write the required code make it invoke once inside the addCustomer() function. Since we already have the service function created inside the customer.service.js file, let’s load the service module inside the middleware through the module file as below just after the module.exports statement.

var CustomerService = require('./customer.module')().CustomerService;

If we add the code to invoke the CustomerService.createCustomer() inside the addCustomer() function, we will have resolved the current error shown as above. 

function addCustomer(req, res, next) {
    CustomerService.createCustomer(req.body);
}

Now we will face another error as the expectation of req.response is not met yet. 

Unit Test 13: CustomerMiddleware - addCustomer() Success Test Case - Failed 3
Unit Test 13: CustomerMiddleware – addCustomer() Success Test Case – Failed 3

To make this behavior passed and createCustomer() is returning the promise function, let’s add the promise handling function for successful operation. Inside this successful promise handler function, assign the data returned from the CustomerService.createCustomer() function to the req.response attribute. Here is the updated addCustomer() function with the latest changes.

function addCustomer(req, res, next) {

    CustomerService.createCustomer(req.body)
        .then(success);

    function success(data) {
        req.response = data;
    }

}

We will notice that one more expectation is satisfied and the ‘undefined’ error would have gone. Now, comes the next and final error for this test spec.

Unit Test 13: CustomerMiddleware - addCustomer() Success Test Case - Failed 4
Unit Test 13: CustomerMiddleware – addCustomer() Success Test Case – Failed 4

This expectation is for the next() function to be called once inside the success handler function of the createCustomer() function’s promise. Just by adding the next() statement after the req.response attribute assignment, all the expectations of the addCustomer() spec would be met, and we will see no more errors at this time.

This addition will also make sure that this addCustomer() middleware function passes the execution control to the next available middleware function in the stack as per the definition of the middleware function.

Here is the completed middleware file content for the test suites that we had written so far.

(function () {
    'use strict';

    module.exports = {
        addCustomer: addCustomer
    };

    var CustomerService = require('./customer.module')().CustomerService;

    function addCustomer(req, res, next) {

        CustomerService.createCustomer(req.body)
            .then(success);

        function success(data) {
            req.response = data;
            next();
        }

    }

})();

So here is the successful test suite’s result for the addCustomer() functionality so far.

Unit Test 13: CustomerMiddleware - addCustomer() Success Test Case - Passed
Unit Test 13: CustomerMiddleware – addCustomer() Success Test Case – Passed

Unit Test 14: AddCustomer Failure Test Case

So far, we have completed the test suite and functional code for the execution path of successful customer addition for the middleware function. We also need to test the negative test case.

Test Script

Here is the complete test spec for the failed execution path for customer creation function of the CustomerMiddleware. Assume there is an error occurred while creating the customer and we want to make sure that error has been appropriately handled without crashing the application. This spec will test that error handling part of the createCustomer() function. 

it('should throw error while creating the new customer', function () {
    expectedError = ErrorFixture.unknownError;

    createCustomerPromise = Promise.reject(expectedError);
    createCustomer.withArgs(req.body).returns(createCustomerPromise);

    CustomerMiddleware.addCustomer(req, res, next);

    sinon.assert.callCount(createCustomer, 1);

    return createCustomerPromise.catch(function (error) {
        expect(error).to.be.a('object');
        expect(error).to.deep.equal(expectedError);
    });

});

To begin with the new spec for testing the error while creating the customer, below it() block is needed.

it('should throw error while creating the new customer', function () {
   
});

Next, the local variables for expectedError and createdCustomerPromise are assigned with the appropriate initial values and behavior. We can assign the unknownError from the error fixture for the expectedError. Also, the promise’s rejected behavior is assigned to the createdCustomerPromise variable. 

Once the required data fixtures have been set up, let’s make the createCustomer() function call with all the inputs as below.

CustomerMiddleware.addCustomer(req, res, next);

Next two statements are for confirming the expected behavior of the error execution path. First, we are making sure that the service function createCustomer() is called from within the middleware’s addCustomer() function.

Finally, we are testing the error handler function for the promise returned from the CustomerService’s createCustomer() function. In this catch block, the thrown error is expected to be equal to the unknownError object.

sinon.assert.callCount(createCustomer, 1);

    return createCustomerPromise.catch(function (error) {
        expect(error).to.be.a('object');
        expect(error).to.deep.equal(expectedError);
    });

After this, if we run the test, we will get the below error. It is because there is no code which satisfies the test case that we just wrote.

Unit Test 14: CustomerMiddleware - addCustomer() Failure Test Case - Failed
Unit Test 14: CustomerMiddleware – addCustomer() Failure Test Case – Failed

Code

We can quickly fix the above error just by adding the catch block to capture and handle the promise error thrown from service call inside the middleware’s addCustomer() function. Below is the modified function with added catch block which invokes the failure() function to handle the error thrown.

function addCustomer(req, res, next) {

    CustomerService.createCustomer(req.body)
        .then(success)
        .catch(failure);

    function success(data) {
        req.response = data;
        next();
    }

    function failure(error) {
        next(error);
    }

}

So finally, we have all the test suites passed for the middleware function addCustomer(), both success and failure execution paths.

Unit Test 14: CustomerMiddleware - addCustomer() Failure Test Case - Passed
Unit Test 14: CustomerMiddleware – addCustomer() Failure Test Case – Passed

As the next step for completing the Add Customer Functionality for the Node.js REST API, we will have to develop the functional code for the Add Customer API endpoint in the next post along with the integration test scripts.

Please check out the index page for this Node.js RESTful API development with TDD approach tutorial with all the posts in sequence in one place.

This blog post is an excerpt from the book Building Node.js REST API with TDD approach. Please check out the link for more information.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.