Mock Network Requests in React Testing Library with Mirage

Use your Mirage server to test your React application under different server scenarios using React Testing Library.

This is a quickstart guide for people already using React Testing Library in their React apps.

Step 1: Install Mirage

First, make sure you have Mirage installed:

# Using npm
npm install --save-dev miragejs

# Using Yarn
yarn add --dev miragejs

Step 2: Define your server

Create a new server.js file and define your mock server.

Here's a basic example:

// src/server.js
import { Server, Model } from "miragejs"

export function makeServer({ environment = "development" } = {}) {
  let server = new Server({
    environment,

    models: {
      user: Model,
    },

    seeds(server) {
      server.create("user", { name: "Bob" })
      server.create("user", { name: "Alice" })
    },

    routes() {
      this.namespace = "api"

      this.get("/users", schema => {
        return schema.users.all()
      })
    },
  })

  return server
}

In a Create React App, put this file in src/server.js so that changes to it trigger rebuilds.

Step 3: Create a test file that uses Mirage

Here's the component we'll be testing.

// src/App.js
import { useState, useEffect } from "react"

export default function App() {
  let [users, setUsers] = useState([])
  let [serverError, setServerError] = useState()

  useEffect(() => {
    fetch("/api/users")
      .then(res => res.json())
      .then(json => {
        if (json.error) {
          setServerError(json.error)
        } else {
          setUsers(json.users)
        }
      })
  }, [])

  return serverError ? (
    <div data-testid="server-error">{serverError}</div>
  ) : users.length === 0 ? (
    <p data-testid="no-users">No users!</p>
  ) : (
    <ul data-testid="users">
      {users.map(user => (
        <li key={user.id} data-testid={`user-${user.id}`}>
          {user.name}
        </li>
      ))}
    </ul>
  )
}

Create a new src/__tests__/App.test.js file, import your makeServer function, and start and shutdown Mirage using beforeEach and afterEach, making sure to pass the test environment to Mirage:

// src/__tests__/App.test.js
import React from "react"
import { render, waitForElement } from "@testing-library/react"
import App from "../App"
import { makeServer } from "../server"

let server

beforeEach(() => {
  server = makeServer({ environment: "test" })
})

afterEach(() => {
  server.shutdown()
})

Step 4: Write tests using your Mirage server

Use your tests to seed Mirage with different data scenarios, then assert against the state of your UI.

In the test environment, Mirage doesn't load its database seeds, so that the server starts out empty for each test run.

it("shows the users from our server", async () => {
  server.create("user", { name: "Luke" })
  server.create("user", { name: "Leia" })

  const { getByTestId } = render(<App />)
  await waitForElement(() => getByTestId("user-1"))
  await waitForElement(() => getByTestId("user-2"))

  expect(getByTestId("user-1")).toHaveTextContent("Luke")
  expect(getByTestId("user-2")).toHaveTextContent("Leia")
})

it("shows a message if there are no users", async () => {
  // Don't create any users

  const { getByTestId } = render(<App />)
  await waitForElement(() => getByTestId("no-users"))

  expect(getByTestId("no-users")).toHaveTextContent("No users!")
})

Step 5: Alter your Mirage server to test different server states

In addition to different data scenarios, you can use your tests to reconfigure your Mirage server to test new situations.

For example, you can test an error state like this:

// src/__tests__/App.test.js
import { Response } from "miragejs"

it("handles error responses from the server", async () => {
  // Override Mirage's route handler for /users, just for this test
  server.get("/users", () => {
    return new Response(
      500,
      {},
      {
        error: "The database is on vacation.",
      }
    )
  })

  const { getByTestId } = render(<App />)

  await waitForElement(() => getByTestId("server-error"))

  expect(getByTestId("server-error")).toHaveTextContent(
    "The database is on vacation."
  )
})

Because of the way we setup Mirage using beforeEach and afterEach, each test will get a fresh Mirage server based on your main server definition. Any overrides you make within a test will be isolated to that test.