For the latest full-stack applications to work, a backend service is required. That is especially true when the frontend service depends on the backend service to render data. In many cases, it can be difficult to replicate the setup of the backend services so that you can test the application. This tutorial will show you how to mock HTTP requests from an API so that you can test endpoints without actually calling them in your tests.

This tutorial covers these topics:

  • Setting up Nock Node library
  • Using Nock to write API mocked tests
  • Enabling actual HTTP requests using Nock

Prerequisites

To get the most from following the steps in this tutorial, you will need to have a few things established,

  1. Basic knowledge of JavaScript
  2. A CircleCI account
  3. A GitHub account
  4. Node.js installed on your system (version >= 10)
  5. Clone the test repository from GitHub

Once you clone the repository, there is no need to set up the application. You can simply follow the tutorial while verifying the steps taken in the file todo.spec.js located in the application root of the cloned repository.

Why should I use mock testing?

A simple client-server architecture web application exists with both a backend and a frontend service. The frontend service contains the presentation logic of the application, and in most cases acts as the user interface. When a user performs an action, the client sends a request to the backend application. Throughout this tutorial I will refer to the backend application as a server.

Mock testing makes the process of running tests faster because you can eliminate dependencies on external systems and servers. Essentially, you “mock up” the backend application. Another benefit of using mocked dependencies is that you can test without causing a strain on external system resources like databases.

Note: Mock testing should always be used alongside other types of testing to provide the best test coverage and to increase confidence in the release process.

Testing an API HTTP architecture

In the sample test we are building, we want to check how long it takes to make a request to an actual back end of a todo application. The request will give us an idea of how much time mocking can save, especially in the case of running our tests when mocking the back end. For a user, the application creates, displays, and fetches all the todo items. For this test we only need to check the time it takes to return all the todo items that have already been created.

describe('todo-app-barkend mocked tests', () => {
   it('can get todos', async () => {
       const response = await getTodoItems.getAllTodos();
       console.log(response)
   })
})

The result shows the amount of time it took to execute the simple test and return the results. Since we are calling the actual ‘database’ we will return all the results rather than only the specific results that we need.

Runtime for Application

It takes about 3.591 seconds to retrieve all the todo items that we have currently added. When running tests, it would then take up about 3.591 seconds to retrieve the todo items every time we want to assert something in the response which would then lead to a lot of overhead and time constraints when running our tests.

Test execution architecture without mocks

Here is a diagram that shows the simplified architecture of how this test runs without any kind of intervention from a mocked service.

Unmocked API testing architecture

Testing this architecture by setting up a test database to ensure that we actually get responses could be a headache. Instead, we can isolate all the backend dependencies to only focus on the frontend application. Then we use the Nock mock service to intercept requests to the API backend and return custom responses.

Mocked test execution architecture

Mocked API testing architecture

This architecture diagram shows that what is returned is not the API response, but a custom response. You do have an option of overriding the mock API responses and hitting the endpoints directly. We will use the Nock library to test how to mock API services and even override the mocked calls.

Before we dive into what Nock is and how to use it, take a moment to set up CircleCI and Git on your repository. If you have already cloned the repository and you have already set up the application this step is not necessary.

Setting up Git and pushing to CircleCI

To set up CircleCI, initialize a Git repository in your project by running the command:

git init

Next, create a .gitignore file in the root directory. Inside the file add node_modules, to keep npm-generated modules from being added to your remote repository. The next step will be to add a commit and then push your project to GitHub.

Log into CircleCI and navigate to Projects. All the GitHub repositories associated with your GitHub username or your organization are listed. The specific repository that you want to set up in CircleCI for this tutorial is api-mock-testing-with-nock.

On the Projects dashboard, click the Set Up Project button. Then, click Use Existing Config.

start-building-page

When prompted, click Start Building. The pipeline fails, which is as expected. We still need to add our customized .circleci/config.yml configuration file to GitHub before the project will build properly.

start-building-prompt

Writing the CI pipeline configuration

Once we have setup CircleCI pipeline, it is time to add CircleCI to our local project. Start by creating a folder named .circleci in the root directory. Inside the folder, create a config.yml file. Now add configuration details:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: cimg/node:10.16.3
    steps:
      - checkout
      - run:
          name: update npm
          command: "npm install -g npm@5"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: install dependencies
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: run api mock tests
          command: npm test
      - store_artifacts:
          path: ~/repo/api-mock-testing-with-nock

In this configuration, CircleCI uses a node Docker image pulled from the environment and then updates npm package manager. The next stage then restores the cache (if it exists) updates the application dependencies when a change has been detected with save-cache. Finally, it runs the api-mock-testing-with-nock tests and stores the cached items in the artifacts api-mock-testing-with-nock directory.

Pushing your changes to GitHub and CircleCI will automatically start the building process. Because we do not have any tests yet, our pipeline will fail (again). That is ok, because we will re-run this after we add the tests. Even without tests, details for the pipeline are available to review.

What is Nock?

Nock is a HTTP server mocking and expectations library. We will use this library to test frontend modules that are performing HTTP requests. We can test in isolation because Nock lets us dictate what our API responses will be.

In this tutorial we will use an existing API todo application to test how it reacts to the backend APIs using Nock. Because we are using a hosted application, we can focus just on the tests and not developing the application itself.

Testing with Nock

Just like in the architectural diagram, Nock sits in between the backend application and the frontend application and intercepts any request being tested. Instead of actually calling the backend service to test the application, we provide a set of known responses that simulate (mock) the application.

In this test, we will rewrite our earlier test mocking the API request that returns the responses for todo items. The test is in the cloned repository in the file todo.spec.js.

it('can get todos', async () => {
       const todoObject = {
           todos: [
               { task: "Two", _id: 9, "completed": false },
               { task: "three", _id: 84, "completed": false }]
       }
       nock('https://todo-app-barkend.herokuapp.com/todos/')
           .get('/')
           .reply(200,
               todoObject
           )
       const res = await got('https://todo-app-barkend.herokuapp.com/todos/')
       expect(res.body).to.eq(JSON.stringify(todoObject))
       expect(res.statusCode).to.equal(200)
   })

Nock intercepts any GET requests that are routed to /todos/ endpoint. Instead of the test using the response from the API, Nock returns the response that was defined using the nock() method.

Note: Heroku free tier dynos sleep after 30 minutes of no activity. If the test fails, the dynos could be inactive and you need to re-run the test.

After the test runs, you can check the run times, and observe that it is quicker to run the test using the mocked service. The mocked service runs in about 1.58 seconds. That is much better than the earlier request without a mocked service, which ran for about 3.59 seconds.

start-building-prompt

Running the new test shows clearly that mocked requests run faster, which can save significant time. That is especially when there are multiple tests that require calling the backend.

Note: While Nock mocks out the API requests, it needs to perform HTTP requests on the endpoints when the tests are executing to ensure that the endpoints are present. Therefore the tests will fail if the endpoints being mocked are not available, or if the specified routes on the tests are invalid.

Bypassing Nock HTTP mocks

While mocked endpoints are useful to help you understand how your application interacts with the backend services, it is also important to be able to bypass the mocked requests in your tests. When you need to verify that you get proper responses from the endpoints when making HTTP requests to the backend service, you must bypass mocking.

In the next sample test, we are using the Nock enableNetConnect method to define links that we can bypass when mocking our tests. A good example would be to enable connection to the localhost URL and mock with the hosted URL just to verify that everything is working correctly. You can find this test in the cloned repository in the file todo.spec.js.

it('can create todos - enabled backend', async () => {
       var options = {
           headers: {
               'content-type': 'application/json'
           },
           body: JSON.stringify({
             task: 'Cook Lunch'
           })
         };
 
       nock.enableNetConnect(/(todo-app-barkend)\.herokuapp.com/)
       const res = await got.post('https://todo-app-barkend.herokuapp.com/todos/', options)
       expect(JSON.parse(res.body)).to.have.property('task', "Cook Lunch");
   })

In the backend test for creating todo items, we have specified the regex for the URL to be bypassed, then made a later request to that URL. The second request is to make sure that the application actually creates a todo item when we make a request to the remote URL.

Note: It is important to clear mocks after running each test and to enable any blocked HTTP requests. Clearing mocks and blocks makes sure they do not interfere with subsequent tests, and that the other tests can make HTTP requests.

To clear mocks after running tests, enter this code in the afterEach section of the tests:

afterEach(() => {
       nock.cleanAll()
       nock.enableNetConnect()
   })

Verifying CircleCI pipeline success

Now that the tests are working and the CI pipeline is set up, you can add all the files to git and push them to GitHub remote repository. Our CircleCI pipeline should kick in and run our tests automatically. To observe the pipeline execution, go to the CircleCI dashboard and click the project name: (api-mock-testing-with-nock).

To review the build status, select the build from the CircleCI dashboard. You will be able to review the status of every step as defined in your CircleCI configuration file.

Successful pipeline run

Fantastic! We have a green build.

Conclusion

In this tutorial I have demonstrated what API mocking is and how useful it can be. We used Nock to mock HTTP requests in our tests. We have demonstrated how mocking is used to reduce the time it takes for tests to execute and shown how to test only the behavior of the application in isolation, without involving external dependencies. We practiced how to run tests with mocks enabled and disabled and learned the value of clearing the mocks that we have set up in the tests. Armed with this knowledge, you are in a better position to test APIs without necessarily calling them for responses. Happy testing!


Waweru Mwaura is a Software Engineer and a lifelong-learner who specializes in Quality Engineering. He is an author at packt and enjoys reading about engineering, finance, and technology. You can read more about him on his web profile.