Most examples that discuss Test-Driven Development don’t include information about how to test components that fetch data. With Jest, we get an environment in Node.js that mimics the browser because it provides jsdom. However, Jest does not describe a “batteries included” vision for server responses. Let’s discuss the best way to test front-end components that make API calls.

Mocks are risky assumptions

I often see examples advising that you mock an entire library. The examples mock axios, request, or fetch to test that a specific function is called. Here’s an example provided by Testing Library using React:

// fetch/fetch.test.js
import React from 'react'
import { render, fireEvent, waitFor, screen } from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'
import axiosMock from 'axios'
import Fetch from '.'


test('loads and displays greeting', async () => {
  const url = '/greeting'
  render(<Fetch url={url} />)

    data: { greeting: 'hello there' },
  })'Load Greeting'))

  await waitFor(() => screen.getByRole('heading'))

  expect(screen.getByRole('heading')).toHaveTextContent('hello there')
Update: Testing Library recommends Mock Service Worker and no longer maintains the example above.

This approach tests implementation details in addition to behavior. It binds our test suite to a library and assumes that the library’s API will not change. It also assumes that we’re using the library method correctly. In this case, our test suite is now bound to axios, and the method get(). If your team wants to switch request libraries from axios to another option such as unfetch, the test example above will need to be re-written to account for unfetch’s API. Say you have 4k tests on a large project? To properly refactor, you will need to re-write all tests that directly mock axios. You will lose your testing baseline which means you will need to follow Red, Green, Refactor across all of the tests you previously wrote. The process of changing your data fetching library will be tedious and prone to errors.

Stub the environment, not the implementation

jsdom is the backbone of UI testing in Jest. Before Jest and React, most front-end developers relied on Angular, Phantom, and Karma. The idea was to spin up an instance of Chrome (which every UI developer was using) and run tests with a live browser. You get the behavior of a single browser, along with its quirks, the effort to ensure the browser can run on a CI pipeline, and the cost of a browser turning on/off while you develop code. jsdom is the sweet spot between running a live browser and mocking individual browser methods in node. It’s portable, follows web specs reasonably well, and doesn’t have as high a cost as a real browser. jsdom stubs the browser environment by implementing everything a browser has on node.

What is the extreme opposite of mocking data fetching libraries and their methods for each test suite? Running a live server. We don’t want to run the live server while testing, so what’s the sweet spot in between mocking our data fetching and running a server? Using a library that follows the HTTP spec, reads incoming data, and allows you to stub responses.

Which API stubbing library should I use?

There are several libraries available to stub server responses:

I recommend nock for several reasons:

Clocking in 170kb, it’s perfect for usage in isolated unit and integration tests. There’s no big installation and no major setup. It’s a mature, well-documented library that gives developers the ability to easily investigate and find answers to their problems. These aspects pay off in spades as a project grows and ages.

Let’s look at nock using the previous example:

// fetch/fetch.test.js
import React from 'react'
import nock from 'nock'
import { render, fireEvent, waitFor, screen } from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'
import Fetch from '.'

test('loads and displays greeting', async () => {
  const url = '/greeting'

  const scope = nock('')
    .reply(200, {
      data: { greeting: 'hello there' },
  render(<Fetch url={url} />)'Load Greeting'))

  await waitFor(() => screen.getByRole('heading'))

  expect(screen.getByRole('heading')).toHaveTextContent('hello there')

In the new and immproved approach, we’ve done several things:

  1. Stopped mocking the axios library and method response
  2. Specified a response at a url and a route
  3. Removed unnecessary assertions about API calls

Our test suite no longer knows the way our components fetch data. If you switch from axios, fetch, or unfetch, the test file will not require changes. More importantly, if you upgrade your data fetching library version, your test suite will give you meaningful feedback. When we mock a dependency, we begin testing with faulty assumptions that the package will not have made breaking changes to its internals or its API, leading to false positives in our test suite.

How to test components using Apollo Client with GraphQL

Two red mail boxes against a brick wall in the

Photo by Kristina Tripkovic on Unsplash

With the rise of GraphQL, Apollo has made significant strides in writing server and client-side libraries to make managing data easier. The trouble comes with their recommended approach to testing UI components that rely on Apollo.

Apollo has created a MockedProvider test component which allows you to test your UI components. They assert that using the live Provider would be unpredictable as it runs against an actual backend. That may be true, but nothing stops us from hijacking the means of communicating with the backend just like we did with the axios example.

Here are the things I know about interfacing with Apollo and GraphQL:

  1. All requests, queries and mutations, use the HTTP POST method. Because GraphQL serves a single resource, the graph, it doesn’t follow the REST resources approach in HTTP (GET/PUT/PATCH/DELETE).
  2. Apollo requires an instantiated client which is essentially a config class for setup.
  3. Apollo ensures type names in the resource response.

I now know about a few details in setting up a proper test:

  1. We need to import the real client to be sure I’m hitting the right endpoint
  2. We need to get some stub data from my endpoint
  3. We’re going to respond to a post request with the stub data

For this example, I will use the free Pokemon list server, grab some fake data, and query against it.

diff --git a/src/App/App.test.js b/src/App/App.test.js
index e69de29..0d66745 100644
--- a/src/App/App.test.js
+++ b/src/App/App.test.js
@@ -0,0 +1,45 @@
+import React from "react";
+import { ApolloProvider } from "@apollo/react-hooks";
+import nock from "nock";
+import { render } from "@testing-library/react";
+import { client } from "./client";
+import { App } from "./";
+describe("App", () => {
+  it("works", async () => {
+    nock("")
+      .defaultReplyHeaders({ "access-control-allow-origin": "*" })
+      .post("/")
+      .reply(200, {
+        data: {
+          pokemon: [
+            {
+              id: "UG9rZW1vbjowMDE=",
+              name: "Bulbasaur",
+              __typename: "Pokemon"
+            },
+            {
+              id: "UG9rZW1vbjowMDI=",
+              name: "Ivysaur",
+              __typename: "Pokemon"
+            },
+            {
+              id: "UG9rZW1vbjowMDM=",
+              name: "Venusaur",
+              __typename: "Pokemon"
+            }
+          ]
+        }
+      });
+    const { findAllByTestId } = render(
+      <ApolloProvider client={client}>
+        <App />
+      </ApolloProvider>
+    );
+    const pokemon = await findAllByTestId("pokemon");
+    expect(pokemon.length).toBe(3);
+  });
diff --git a/src/App/client/client.js b/src/App/client/client.js
index 8b13789..b586022 100644
--- a/src/App/client/client.js
+++ b/src/App/client/client.js
@@ -1 +1,7 @@
+import ApolloClient from "apollo-boost";

+const options = {
+  uri: "",
+export const client = new ApolloClient(options);

You may be thinking, “this looks like a lot of setup in comparison to using MockedProvider as recommended”. You’re not wrong. We now know about some of the implementation details of how Apollo fetches data from the server (POST). However, I would argue that this minor detail is what we need to know to have confidence in our tests and the confidence to make changes. The GraphQL server expects us to perform a POST operation, and if we decide to no longer use Apollo, we have some safety.

Swapping Apollo Client for Fetch

Here’s what it looks like if we no longer want to use Apollo Client and opt for a more close to the metal solution using isomorphic-unfetch:

diff --git a/package.json b/package.json
index 6cbd5e4..00e3a3a 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
     "@testing-library/react": "^10.0.4",
     "apollo-boost": "0.4.9",
     "graphql": "15.0.0",
+    "isomorphic-unfetch": "^3.0.0",
     "nock": "12.0.3",
     "react": "16.12.0",
     "react-dom": "16.12.0",
diff --git a/src/App/App.js b/src/App/App.js
index e9f0350..25a0b4f 100644
--- a/src/App/App.js
+++ b/src/App/App.js
@@ -1,8 +1,7 @@
-import React from "react";
-import { gql } from "apollo-boost";
-import { useQuery } from "@apollo/react-hooks";
+import React, { useEffect, useState } from "react";
+import fetch from 'isomorphic-unfetch';

-const ALL_POKEMON = gql`
+const ALL_POKEMON = `
     pokemon(first: 3) {
@@ -16,9 +15,24 @@ function List({ items = []}) {

 export function App() {
-  const { data, loading } = useQuery(ALL_POKEMON);
+  const [isLoading, setIsLoading] = useState(true);
+  const [data, setData] = useState(undefined);

-  if (loading) return <div>Loading...</div>;
+  useEffect(() => {
+    const controller = new AbortController();
+    async function fetchPokemon() {
+      try {
+        const result = await fetch('', {
+          method: 'POST',
+          headers: {'Content-Type': 'application/json'},
+          body: JSON.stringify({query: ALL_POKEMON}),
+          signal: controller.signal,
+        });
+        const json = await result.json();
+        setData(;
+      } catch(e) {
+        console.error(e);
+      }
+    }
+    fetchPokemon();
+    setIsLoading(false);
+    return () => {
+      controller.abort();
+    }
+  }, []);
+  if (isLoading) return <div>Loading...</div>;

   return (
diff --git a/src/App/App.test.js b/src/App/App.test.js
index 0d66745..6fd98df 100644
--- a/src/App/App.test.js
+++ b/src/App/App.test.js
@@ -1,9 +1,7 @@
 import React from "react";
-import { ApolloProvider } from "@apollo/react-hooks";
 import nock from "nock";
 import { render } from "@testing-library/react";

-import { client } from "./client";
 import { App } from "./";

 describe("App", () => {
@@ -33,11 +31,7 @@ describe("App", () => {

-    const { findAllByTestId } = render(
-      <ApolloProvider client={client}>
-        <App />
-      </ApolloProvider>
-    );
+    const { findAllByTestId } = render(<App />);

     const pokemon = await findAllByTestId("pokemon");

Notice that my test assertions didn’t change. I’ve hollowed out the innards of the production code and was able to retain the test suite. The test suite does not care about how my app fetches data as long as I follow the server’s contract.

The goal of having a robust test suite is the ability to make changes confidently and receive feedback if we make changes that might cause problems. By removing mocks and stubbing the server, we can create a flexible test suite that ensures a server contract is maintained.

Testing with multiple requests

Using GraphQL means we can’t stub a single endpoint and respond with different data. With HTTP interceptors like nock, we assign one endpoint to one response. We must control the flow of data by checking the incoming query. Without checking the incoming data, we’ll end up responding incorrectly to the different calls.

diff --git a/src/App/App.js b/src/App/App.js
index 25a0b4f..691c47a 100644
--- a/src/App/App.js
+++ b/src/App/App.js
@@ -1,7 +1,7 @@
 import React, { useEffect, useState } from "react";
 import fetch from 'isomorphic-unfetch';

-const ALL_POKEMON = `
+export const ALL_POKEMON = `
     pokemon(first: 3) {
diff --git a/src/App/App.test.js b/src/App/App.test.js
index 6fd98df..5304b3c 100644
--- a/src/App/App.test.js
+++ b/src/App/App.test.js
@@ -2,13 +2,13 @@ import React from "react";
 import nock from "nock";
 import { render } from "@testing-library/react";

-import { App } from "./";
+import { App, ALL_POKEMON } from "./";

 describe("App", () => {
   it("works", async () => {
       .defaultReplyHeaders({ "access-control-allow-origin": "*" })
-      .post("/")
+      .post("/", JSON.stringify({query: ALL_POKEMON}))
       .reply(200, {
         data: {
           pokemon: [

I’ve now added a data constraint to the post method in nock. If I don’t pass data that matches the constraint, I will not receive the 200 reply and data. The GraphQL spec advises that you pass an object with two specific parameters; query and variables. In this particular case, we’re sending just the query. With our request constraint added, we’re now free to add additional responses.

Deciding tradeoffs

The solutions I’ve proposed are ultimately about tradeoffs. As your software changes, you have to decide which parts you are comfortable living with, no matter the scale. For some people, the notion of managing a server response library is more painful and tedious than just mocking libraries and responses. For me, the pain of not having confidence in my test suite far outweighs the trivial tedium of using nock.

I’ve felt the pain of migrating a codebase from one library to another, including libraries that fetch data. I hope this guide helps you evaluate the tradeoffs in mocking dependencies versus stubbing environment responses.