Skip to content

Mocking HTTP requests with Mock Service Worker

Introduction

Mock service worker is a fantastic tool for mocking network requests (supports REST and GraphQL requests). It works by intercepting network requests and sending back mocked responses to the client. In the browser, it intercepts requests using a service worker, and in Node environments using the node-request-interceptor library.

You might have experience mocking network requests by spying on fetch or XMLHttpRequest methods in the past. With mock service worker, all you need is to match a request and provide a handler function that returns a mocked response. In practice this ends up providing a really simple and intuitive developer experience.

There are a few areas where MSW can really help to level up your workflow:

  • Mocking HTTP requests in test environments
  • Creating Storybook stories for components that rely on HTTP requests
  • Using mock requests to enable offline support for your app during development

Examples and Integrations

If you’re wanting to integrate mock service worker with Storybook or some other framework, they have an "examples" page with links to example repos. There are examples for some of the popular front end frameworks like React, Angular, and NextJS, as well as examples for tools like Storybook. Generally speaking, you can use it any browser or node environment following the setup instructions described in the next section.

Setup

Before we can start mocking http requests, we need to get Mock Service Worker setup for the environments that we want to use it in. The integrate page in the documentation has instructions for both Browser and Node environments.

In general, the setup process involves:

  • Creating a service worker or server by passing our request handler functions as an arg to the setupWorker or setupServer functions respectively.
  • Start the MSW by calling the function returned from the initial setup step worker.start() or server.listen()

Typically, you will only want to have Mock Service Worker running on test or dev environments. You will most likely want to start the server or service worker based on a condition like process.env.NODE_ENV. In one of my projects, I enable Mock Service Worker with my tests or if a MSW_ENABLED flag is set on my environment. I like having the control of being able to enable it during development conditionally.

Request mock basics

Request mocks with Mock Service Worker consists of a request matcher and handler.

In the example below, /posts would be the matcher, and the callback function would be the handler. The MSW REST API includes a method for the different request method types: get, post, put, patch, and delete. We return a response that is a JavaScript object that will be serialized into json by ctx.json.

rest.get('/posts', (req, res, ctx) => {
  return res(
    ctx.json([
			{
	      id: 'post-1',
	      title: 'Post One',
	      content: 'A post about stuff and things',
	    }
		]),
  )
})

We can even match requests using parameters and wildcards:

req.get('/posts/:postId', (req, res, ctx) => {
	const { postId } = req.params;
	return res(
		ctx.json({
			id: postId,
			name: 'A post about nothing'
			content: 'Sometimes nothing is everything and everything is everything'
		})
	);
});

Using parameters in our request matching means we don’t need to provide an exact match for our requests. The /posts/:postId will match any value, and it will be available on the req.params object in our handler function.

If you’ve ever used express before to build Node APIs, this probably looks pretty familiar to you. MSW allows us to write our request mocks in a similar way that we write our actual API endpoints.

Mocking GraphQL requests

Mocking GraphQL requests with MSW is very similar to mocking REST requests. The API includes a method for queries graphql.query and mutations graphql.mutation. Instead of passing a path for our request matcher, we match the query or mutation name ie: GetAllPosts. [ctx.data](http://ctx.data) is used to return a response from our mocked GraphQL query.

graphql.query('GetAllPosts', (req, res, ctx) => {
  return res(
    ctx.data({
      posts: [
				{
		      id: 'post-1',
		      title: 'Post One',
		      content: 'A post about stuff and things',
		    }
			],
    }),
  )
})

We can also return GraphQL errors from our query and mutation mocks:

graphql.mutation('Login', (req, res, ctx) => {
  return res(
    ctx.errors([
      {
        message: 'Failed to log in: username or password are invalid',
        locations: [
          {
            line: 8,
            column: 12,
          },
        ],
      },
    ]),
  )
})

This is very useful for mocking and testing error states in our application. In the next section, we’ll introduce using request params to mock alternate states like this.

Using request params to mock different states

This is a technique that I’ve found especially useful for tests and Storybook. For a basic example, let’s pretend we’re mocking API requests for a page that lists an author’s blog posts. Here are some of the states we might have for this view:

  • An error state for failed requests
  • An empty state if this author doesn’t have any posts
  • A list of the author’s posts

We can utilize request to return different responses to test these alternate states in our UI. In this example, we return different mocked responses for error, empty, and default states based on the authorId search param.

rest.get('/posts', (req, res, ctx) => {
	const authorId = req.url.searchParams.get('authorId');

	if (authorId === '0') {
		return res((res) => {
			res.status = 400;
			return ctx.json({
				error: 'Invalid author ID'
			}),
		});
	}

	if (authorId === '1') {
		return res(ctx.json([]));
	}

  return res(
    ctx.json([
			{
	      id: 'post-1',
	      title: 'Post One',
	      content: 'A post about stuff and things',
	    }
		]),
  )
})

We can now make a query fetch('/posts?authorId=0')to test the error or empty states in our application in addition to the happy path.

Dynamically generating mock data

These powerful features available to us for mocking requests can be combined with a library like Faker.js to easily build out a powerful and dynamic mocking experience for your tests and offline mode.

Looking at the previous example, we would get the same mock data for every single post. This could be improved by randomly generating our response data to match what we might get from the real API.

import { faker } from '@faker-js/faker';

req.get('/posts/:postId', (req, res, ctx) => {
	const { postId } = req.params;

	return res(
		ctx.json({
			id: postId,
			name: faker.lorem.words(),
			content: faker.lorem.sentences(),
		})
	);
});

We could mock the post list endpoint like this:

import { faker } from '@faker-js/faker';

req.get('/posts', (req, res, ctx) => {
	const { postId } = req.params;

	const posts = Array.from({ length: 20 }).map((_, i) => ({
		id: i,
		name: faker.lorem.words(),
		content: faker.lorem.sentences(),
	});

	return res(ctx.json(posts));
});

This makes the code for generating a list of 20 different post terse and unique. We don’t need a long static array defined for our mock posts list, and we will get different values on fields for each item in the list.

Summary

Mock Service Worker provides an easy and powerful way to mock http requests. Combining it with a library like Faker.js allows you to randomly generate your mock data using JavaScript code.

MSW provides some awesome benefits for mocking requests:

  • Mock both REST and GraphQL APIs.
  • You can return different responses based on input conditions.
  • Use wildcard and request params to match and mock requests.

We can use our mocks to power tests, Storybook, and offline support for development. If you haven’t tried MSW yet, I hope this post will motivate you to give it a try.