API Mocking Strategies for JavaScript Applications
Tags: javascript, api, testing, mocking
Published: 2021-03-16 09:08:30

API mocking is easily the most common kind of mocking in JavaScript (apart from the mocking about how there's a new framework every day). No matter what application you're building, it is likely to make some HTTP calls. 

So why do you even need mocking? A good question, [perhaps you don't](https://dev.to/kettanaito/when-should-i-not-use-mocks-in-testing-544e). However, if you wish to test the code you write, you'd also have to test the code that does HTTP requests. Making actual requests in your tests is a sure path to flaky and unreliable tests, and here's where API mocking comes in.

## The Request's Journey

Before we rush into code, let's stop for a moment and think about what we're trying to achieve here.

Mocking is the action of substituting a piece of software with another, seemingly compatible piece. Since we wish to mock an API call, we will be substituting _some piece_ of the logic responsible for making that call. To better understand which part and why we'll be mocking, we need to visualize a journey of the request first.

**Here's a high-level overview of what happens when your application makes a request:**

![The request's journey](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rgh5db7ha50vet8gu9w4.png)

1. Your application calls a _request client_ to make a request. This can be `window.fetch`, `react-query`, or Apollo. 
1. The request client forms a proper request based on your input (resource path, query parameters, headers, body, etc.), and sends it over HTTP to the _server_.
1. The server receives a request and returns a _response_.
1. Your application receives the response.
 
> It's important to mention that this diagram may differ based on the application architecture you have. However, there always will be these fundamental pieces: a logic that dispatches a request and a server that processes that request. 

This overview gives us some visual clues towards what parts conclude a request. We will be mocking one of those parts to achieve our goal. Now to decide which one...

## API mocking strategies

> I've been honored to speak about various API mocking strategies at this year's TestJS Summit conference. In the video below you can get a grasp over mocking as a technique, its purpose, and the most common (_and the most efficient_) ways to achieve it. 

{% youtube 76yEzm07Kfk %}

> Let's take a closer look at each strategy in the written form today.

Out of the request's journey components (application, request client, server) we surely don't wish to mock an application. That's the code we're trying to test, and mocking it would be, well, pointless. 

This leaves us with the two remaining parts, each representing a mocking strategy:

1. Mocking the request client.
1. Mocking the server.

Both these strategies are applicable and widely used. Just as any decision in programming, choosing any of these strategies comes with benefits and drawbacks. Let's focus on those.

### Mocking the request client

When you mock the request client, you're making the following change in the request's journey:

![The request journey altered by the mocking request client strategy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m38dz9a60knsygfu31zj.png)

By mocking (substituting) the request client, the _actual_ request client is taken out of the equation. A compatible client is placed in its stead, giving us the desired power to mock the responses our application receives.

The most basic example that illustrates this mocking strategy in practice is when you stub the `window.fetch` function:

```js
window.fetch = (init, input) => {
  // Respond with a mocked response
  // any time our code calls "window.fetch".
  return new Response('hello')
}
```

Of course, most of the time you would use a third-party library that abstracts this logic for you. Some of the prominent libraries for this strategy are:

- [MirageJS](https://github.com/miragejs/miragejs), through [Pretender](https://github.com/pretenderjs/pretender/blob/e4fa32ab491f5479bb1d0440be1f64e2904994c1/src/index.ts#L32)
- [nock](https://github.com/nock/nock), through [stubbing `http`/`https` modules](https://github.com/nock/nock/blob/2134a418c358d4134233ffaf7bb25809a5869a71/lib/common.js#L93-L110)
- [PollyJS](https://netflix.github.io/pollyjs), through various [adapters](https://github.com/Netflix/pollyjs/tree/cbca602a5a446da46a4a2834f893670b8c577880/packages/%40pollyjs)

#### Benefits

This strategy gives us control over the requests/responses at the earliest stage of their flow. The cost of such control is also minimal, as the mocking logic lives entirely in the client-side code, next to our application.

#### Drawbacks

If you take a closer look at the diagram altered by this strategy, you'll notice that not only the "Server" part is blackened out, but also the "Request" part. That is because replacing the request client means **it never actually makes a request**. If a request "leaves" the client, it won't be able to control it anymore.

There's also a behavioral divergence for your application: it _does_ a request in production, but it _doesn't_ in the tests. 

### Mocking the server

Alternatively, we can replace the "server" part of the request journey to make it look like this:

![Mocking server strategy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xcyjxjyk17wq4dum5twr.png)

Since our request client is configured to communicate with a production server, such request destination detour often happens by introducing some sort of conditional URL logic:

```js
const IS_TEST = process.env.NODE_ENV === 'test'

fetch(
  IS_TEST
    // Communicate with a local mock server while testing.
    ? 'http://localhost:3000/api'
    : 'https://api.backend.com'
  )
```

Some of the most prominent libraries for this mocking strategy are:

- [JSON Server](https://github.com/typicode/json-server)
- [Postman's mock servers](https://learning.postman.com/docs/designing-and-developing-your-api/mocking-data/setting-up-mock/)
- Plain [ExpressJS server](https://expressjs.com/)

#### Benefits

With the mocking server strategy, we're allowing the request client to fully execute because it's the server part that we're placing. This makes our application behavior under test _almost idenctical_ to the one in production. Almost.

#### Drawbacks

For this strategy to work, our request client must know when and how to decide which server endpoint to use. Regardless of the technical details of this strategy, it means that the requests are hitting _an entirely different server_. Introducing any kind of deviation puts the logic you're resting at risk. Consider this:

```js
fetch(
  IS_TEST
    ? 'http://localhost:3000/api'
    : 'hts://apibackendcom' // Ehm, is this okay?
  )
```

The actual production server URL is corrupted in the example above. Guess what, the tests would still pass while the application would be successfully broken for your users.

There is also a minor operational hassle, as you need to spawn and terminate the local mocking server before/after your test suites. You must ensure the mocking server's operability so that there are no unhandled exceptions that may fail your tests, resulting in false negatives.

Overall, introducing an entire server for the sake of mocking may be considered an overhaul. Even if you're using it for this specific purpose, it's still an actual server you need to write and maintain, increasing the operational cost of this setup.

> Using third-party libraries often abstracts the operational aspect of the mocking server, so you're not experiencing it directly. That doesn't mean that it's magically gone. 

## Which strategy to choose?

The mocking strategy you choose largely depends on the environment where you wish to mock API. Certain environments, such as the browser, allow you to intercept requests on the network level via [Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API). That way you compromise on neither request client nor server, allowing _all_ your logic to execute, hit the same production endpoints, and receive the mocked response you're in charge of.

> Libraries like [Mock Service Worker](https://github.com/mswjs/msw) leverage this unique browser ability gracefully to provision seamless API mocking. Let me know if you would like to learn more about this unusual strategy in the comments below!

There are, however, environments that don't have a designated API to intercept outgoing requests. Running tests in Node.js, for example, would likely require your mocking setup to stub request issuing modules (such as `http.request`) in order to know what requests are happening, and mock their responses.

---

## Afterword

Regardless of which API mocking strategy you end up choosing, please remember that:

- The less test-specific setup you have, the better;
- The less your app's behavior changes for the sake of tests, the better;
- The closer to the server your request interception is, the better.

I hope you've enjoyed this analysis of different API mocking strategies you may adopt. Make sure to [follow me on Twitter](https://twitter.com/kettanaito) to stay in touch with the articles I write. Stay productive!