logo logo

Mocking Dependencies in Integration Tests with Nock

Mocking Dependencies in Integration Tests with Nock

The software industry is trending toward integration tests for their ability to provide code confidence. However, they are tedious to set up and external influences a fundamental part of their purpose can reduce performance and trust. Mocking third-party dependencies in integration tests with nock helps reduce complexity and improve consistency and reliability.

Integration Tests with Nock

Nock is a JavaScript library that mocks external API requests. This library is powerful for integration tests because it has no bearing on internal implementations. Outgoing API requests are not part of internal details they’re contracts.

Example

The following diagram is a contrived example of a TODO API that creates an event to schedule a reminder.

A client calls the todo API, which inserts into the todo database. As a side effect, the event API  inserts into the event database.
A client calls the todo API, which inserts into the todo database. As a side effect, the event API  inserts a reminder into the event database.

 

A developer can write a test for this architecture without ever looking at the code. By using test-driven development, they can test behavior rather than unimportant implementation details. This aligns closely with how the library is intended to be used.

The first test is simple:

it("creates a todo", async function () {
  const response = await request(app)
    .post("/v1/todo")
    .set("Content-Type", "application/json")
    .set("Accept", "application/json")
    .send({
      title: "Make resolutions",
      reminder: null,
    });

  expect(response.status).to.equal(201);
  expect(response.body.id).to.be.a.uuid("v4");
  // Additional assertions
});

This test is not using nock, to demonstrate that it may not be appropriate everywhere. Although the test relies on an active database, it’s simple to spin up a Docker container locally or as part of a CI pipeline. While we could mock the database interaction, it could do more harm than good. Developers of popular databases package dependencies in well-maintained and stable Docker containers. Mocking all their interactions would be tedious and could mask fatal errors.

The next test involves interaction with an external API:

it("creates a reminder", async function () {
  let eventApiCalled = false;

  nock("https://events-api:3000")
    .post("/v1/reminder")
    .reply(201, (uri, requestBody) => {
      eventApiCalled = true;
      return { id: "some-uuid", other: "attribute" };
    });

  const response = await request(app)
    .post("/v1/todo")
    .set("Content-Type", "application/json")
    .set("Accept", "application/json")
    .send({
      title: "Make resolutions",
      reminder: {
        date: "2020-01-01T00:00:00Z",
      },
    });

  expect(response.status).to.equal(201);
  expect(eventApiCalled).to.be.true();
});

In this case, the contract between the todo API and the events API is known. Mocking makes sense because the events API can have its own set of tests against the same contract.

By mocking with nock, the events API can be decoupled from the todo API. Some may not consider this to be a true integration test, but including a real connection comes with drawbacks.

Disadvantages of a real HTTP connection

Developers need to run the external API locally and as part of the CI pipeline

This can quickly spiral out of control. The events API could be dependent on five other services, which could depend on ten other services. If those services are not optional, each one will also need to be included in the setup.

Dependencies couple the todo API to the event APIs architecture

To run the events API properly, according to the example diagram, a local database must be included. Other required components, such as a Redis cache or an external logging system, could also be present. If the maintainers of the events API decide to swap the database from MySQL to Postgres, the CI pipeline for the todo API will break. The tests produce a false negative, and don’t reflect reality: the todo API would function properly with the swapped database.

Latency adds time to the tests

By calling out to a real API, especially if it is slow, each test takes slightly longer. While this may seem trivial for a few tests, the delay can accumulate over the lifespan of the product.

Disadvantages of a mocked connection

The todo API must trust the events API to not break the contract

Frameworks like Pact JS can help overcome this issue for internal APIs, but not for third-parties. I purposely did not focus on Pact for this article because many developers have difficulty gaining support and buy-in for it.

💡 For more information on Pact, I recommend exploring this extensive Consumer Contract Testing guide.

Other tips and tricks

The tests consume nocks from a stack

If a test is calling an API twice, the nock is only valid for the first call. To bypass this behavior, the nocks can be declared with persistence:

nock("https://event-api:3000")
  .post("/v1/events")
  .reply(201, function () {
    // Create and return event
  })
  .persist()

However, the default behavior can be useful for exposing unexpected calls. With the persist option excluded, creating two events would fail the test.

Regex covers dynamic urls

nock(/.*amazon.com.*/)
  .put(/s3/)
  .reply(200, {...}

This can help overcome urls with UUIDs.

Nock matches urls exactly without regex, and any calls that are unmatched are unhandled and throw an error.

Query parameter bypass

nock("https://events-api:3000")
  .get("/v1/events")
  .query(true)

Ignoring the query parameters can provide a way to return the same data regardless of parameters.

Replying with an error

nock("https://events-api:3000")
  .post("/v1/reminder")
  .reply(500, "...")

This is another advantage of mocking. Consider the todo API example: if the call to create a reminder fails, it should rollback the transaction that creates the todo and return a 500 error. In a real-world connection, it may be difficult to come up with a natural way to fail the event API, other than turning the service off.

There are many more utilities provided by the library, which are mentioned in the documentation: https://github.com/nock/nock.

Conclusion

Nock changes integration tests to focus less on actual connectivity and more toward adhering to a contract. By mocking the API layer, we can control interactions with third-party services, even if they’re internal to a company. Writing integration tests with nock provides significant gains in speed and test reliability, at the cost of having to trust contracts.

Avatar

About the author

Kevin Fawcett

Programming is my passion. I continuously pursue knowledge, regularly exploring new technologies, and methodologies. Over the years, I have collected experience with design patterns, best practices, and architecture that I enjoy teaching others. Mentoring reinforces my learning.

Join TestProject Community

Get full access to the world's first cloud-based, open source friendly testing community. Enjoy TestProject's end-to-end test automation Platform, Forum, Blog and Docs - All for FREE.

Join Us Now  

Leave a Reply

popup image

A new world for test automation

Join 150,000 testing & dev teams taking their web & mobile testing to new heights, using #1 FREE test automation platform, designed to help deliver quality at speed.
Get Started
FacebookLinkedInTwitterEmail