Get Customer List API – CustomerService – Get Customer List Unit Tests – Node.js API with TDD Tutorial

Once we added the customer data in the backend database, we should be able to query the available customers whenever needed. So, as described in the Low-level design of the Get Customers List functionality, the GET HTTP method will be used to call the API endpoint ‘/customers‘ with any search, filter, and pagination parameters. 

CustomerService – Fetch Customers Unit Tests

In this blog post, we will go through the unit and integration tests scripts and functional coding without any additional functionalities such as search, filter, and pagination.

Unit Test 15: FetchCustomers Success Test Case

We will start by adding test scripts for the customer.service.js file.

Test Suite

To add the test suite for testing the fetchCustomers method in the service file, we will create the describe() block in the customer.service.spec.js file under the /tests/unit/customer directory. 

As already we have the createCustomer test suite in this file, so, let’s add the below code block just below the existing test suite inside the main describe() block of the CustomerService.

describe('fetchCustomers', function () {
	var expectedCustomers, expectedError;
});

The declared local variables expectedCustomers and expectedError are for setting up the expected data for testing the response from the service call once it’s completed.

Test Fixture

If you look at the fixture file /tests/fixtures/customer/customers.json, you will notice that it’s still empty. So, we need to update this file with a list of customers test data for this specific spec. Just fill up this file with the below content, to begin with.

[
  {
    "_id": "5a5ea8fde43c771e4aa5ea06”,
    "firstName": "John",
    "lastName": "Smith",
    "email": "john.smith@example.com",
    "phoneNumber": 9876543210,
    "address": "100 E Street",
    "city": "New York",
    "state": "NY",
    "zipCode": "10000",
    "country": "USA",
    "__v": 0
  },
  {
    "_id": "5a5ea8fde43c771e4aa5ea07”,
    "firstName": "Jane",
    "lastName": "Smith",
    "email": "jane.smith@example.com",
    "phoneNumber": 9876522222,
    "address": "100 E Street",
    "city": "New York",
    "state": "NY",
    "zipCode": "10000",
    "country": "USA",
    "__v": 0
  }
]

Test Script

As the first spec for the fetchCustomers method, we can start writing the test scripts for the successful flow of fetching all the customers that are already created in the MongoDB collection via the createCustomer method. This requires the below it() block with necessary details setup initially before any expected behavior of this function is scripted. 

it('should successfully fetch all customers', function () {

});

As with any unit test cases, this one should also follow the same steps for testing the fetchCustomers() method of the CustomerService module. The three steps are as follows: 

  1. Setting up the expected response of the method under test.
  2. Mocking or stubbing the behavior of the dependencies of the method under test.
  3. Invoking the method that is being tested and confirmed the expected behavior by verifying the response against the expected response.

Let’s start with setting up the expectedCustomers variable by fetching customers data from the CustomerFixture as below.

expectedCustomers = CustomerFixture.customers;

Next, we have to set up the dependency of the fetchCustomers() function. As with the createCustomer() function, the same CustomerModel is the dependency on this method also. There is already a mock object CustomerModelMock created for this model object; we could continue to set up the behavior for fetching the customers from the customers’ collection. Here is the completed code for this step.

CustomerModelMock.expects('find')
    .withArgs({})
    .chain('exec')
    .resolves(expectedCustomers);

What we are doing here is, adding a method called ‘find‘ to the CustomerModelMock with the empty object ‘{}‘ as an argument. Then, adding another ‘exec‘ by chaining it to the previously added method. Finally, setting this behavior to resolve a promise with the expectedCustomers fixture data as the response for the promise. If you notice that we added a new term ‘chain‘ to the mock behavior to chain the multiple methods. It is new from the mock that we set up so far.

We are using two of the Mongoose model’s methods find and exec here. The find method returns the query object with empty object {} as the query parameter, which means when this query is executed later it will return all the documents from the customers’ collection. The next method ‘exec‘ is to execute the previously formed query object through the ‘find’ method call. This second method will execute the query and returns the result.

Also, you might have observed that this method was set to return a promise when it gets executed. That’s because the ‘exec‘ method will return a promise when there is no callback method is passed to it. This how we expect our fetchCustomers() function to behave when we implement the functional logic later on.

To use the .chain() method, we need to import a new module ‘sinon-mongoose‘ which extends the Sinon stubs for Mongoose methods to test any chained methods. So let’s import this new module to the package.json as a development dependency by running the below statement in the command line.

npm install sinon-mongoose --save-dev

Also, add the requiring the module within this spec file just below sinon variable declarations. So updated lines will look as below.

var sinon = require('sinon');
require('sinon-mongoose');

As the last step, we need to invoke the fetchCustomers() method of the CustomerService and verify the Mock object’s expectation as defined. Also, we can further check to confirm that the method under test returns the expected output as defined in the expectation as well. Let’s look at the below-completed code for the final step.

return CustomerService.fetchCustomers()
    .then(function (data) {
        CustomerModelMock.verify();
        expect(data).to.deep.equal(expectedCustomers);
    });

Here, we are invoking the fetchCustomers() method, as we are testing the successful execution path, we have the then() block to handle the promise’s returned data. Inside this block, we are verifying the mocked model’s behavior just by calling the verify() function of the CustomerModelMock object and also confirming the returned data from by the promise is same as the expectedCustomers data. It will make sure the expected behavior of the fetchCustomers() method is as we defined.

As usual, below will be the error message when running the mocha test now.

Unit Test 15: CustomerService - fetchCustomers() - Success Test Case - Failed 1
Unit Test 15: CustomerService – fetchCustomers() – Success Test Case – Failed 1

Code

So, now we got the error message which mentions there is no function called fetchCustomers() in the CustomerService, lets starting writing the functional code for fetchCustomers() method by adding to the customer.service.file.

To solve the above issue, let’s add the new function fetchCustomers() with empty content and add it to the module.exports() object as below.

module.exports = {
    createCustomer: createCustomer,
    fetchCustomers: fetchCustomers
};

Also, the empty function.

function fetchCustomers() {
}

This would have fixed the error mentioned above but yet yielded the new error message as below.

Unit Test 15: CustomerService - fetchCustomers() - Success Test Case - Failed 2
Unit Test 15: CustomerService – fetchCustomers() – Success Test Case – Failed 2

The new error shows that fetchCustomers() method should return a promise so that test spec can invoke this method and handle the successful response with the then() function.

So, let’s add the required code to query all the documents and execute the query and then return the promise of the query execution. Below is the completed code for this function.

function fetchCustomers() {
    return CustomerModel.find({})
        .exec();
}

By completing the fetchCustomers() function with the required code will show that spec’s expectation is met and we finished the unit test for the successful execution path for the method under test. You will see the test results below when you rerun the test.

Unit Test 15: CustomerService - fetchCustomers() - Success Test Case - Passed
Unit Test 15: CustomerService – fetchCustomers() – Success Test Case – Passed

Unit Test 16: FetchCustomers Failure Test Case

In this section, we will write the spec for the negative test case for fetchCustomers() method of the CustomerService to make sure that the failure execution path also handled properly by the functional code.

Test Script

To test the failed execution path for the method under test will require all the same steps as the successful spec except few things like it will expect an error thrown by the invoked method and the error will be handled by the catch() method of the promise object which was thrown by the mocked model object.

Here is the completed spec for this test case. It() block describes that this spec is for testing the error handling the behavior of the fetchCustomers() method. As a first step, we are setting up the expectedError from the ErrorFixture. Next would be the mocked CustomerModel object’s error throwing behavior with all other conditions, like the input and the chaining of the invoked methods of the dependent model object, are the same as the previous test for successful execution path. The returned promise will be coming up with the error output for this use case.

it('should throw error while fetching all customers', function () {
    expectedError = ErrorFixture.unknownError;

    CustomerModelMock.expects('find')
        .withArgs({})
        .chain('exec')
        .rejects(expectedError);

    return CustomerService.fetchCustomers()
        .catch(function (error) {
            CustomerModelMock.verify();
            expect(error).to.deep.equal(expectedError);
        });
});

Finally, the confirmation of the behavior for the failure within the fetchCustomers() method. Here, we are comparing the error returned by the CustomerService’s method and comparing it to the expectedError as per the testing fixture data. 

Now, you would wonder if this test case shows an error message when the test runner runs next time.

Code

It wouldn’t and shouldn’t show any more error message in the test results. Because all the functional code written so far will be sufficient for this test script as well. There is no additional functional code needed for testing the failed execution path for the fetchCustomers()’s functionality.

Below is the screenshot of the test results after the latest test spec added.

Unit Test 16: CustomerService - fetchCustomers() - Failure Test Case - Passed
Unit Test 16: CustomerService – fetchCustomers() – Failure Test Case – Passed

Check out the next Unit Test & Code development for the Middleware function for this API functionality.

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.