Mocking API servers with Mock Service Worker - MSW
A mock API server is an imitation or clone of a real back-end server that mimics a real server by intercepting requests and providing realistic responses.
Mock API servers are helpful during development and testing when a live server is unavailable or does not need to be polluted with test data.
Mock Service Worker (MSW) is an API mocking library that uses the Service Worker API to intercept requests. This tutorial will teach you how to create a mock API server for your applications using MSW.
Setting up Your Development Environment
To get started with MSW, install it in your project’s root directory by running the command below:
npm install msw
Next, create a mocks
folder in your src
folder by running the command below:
mkdir mocks
This folder will contain all the files related to the mock API server.
Finally, create a handler.js
and a browser.js
file in your mocks
folder. The handler.js
file will contain all the route handler logic for the application. The browser.js
file is where you will configure and start your service worker for request interceptions.
Defining REST Mocks
Since you’re mocking a REST API, in your handler.js
file, import rest
from msw
.
Like so:
//handler.js
import { rest } from "msw";
rest
is a namespace exposed by the MSW library that contains essential methods for mocking a REST API, such as get
and post
, which you can use to handle HTTP requests like GET and POST.
Next, create and export an array, handlers
.
Like so:
//handler.js
export const handlers = [];
This array will contain all the mock route handlers for your application. In this tutorial, you will create, Create, Read, Patch, and Delete (CRUD) mock endpoints for your application.
Next, add the following code to your handler.js
file:
const blogs = [
{
"id": 1,
"title": "sunt aut facere",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum"
},
{
"id": 2,
"title": "qui est esse",
"body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel"
},
{
"id": 3,
"title": "ea molestias quasi",
"body": "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur"
}
];
The code block above will serve as a database for your mock API server. Note that your test data should implement the same schema your real API server will implement to ensure that your mock server produces more realistic results.
Next, create a mock route handler for a GET request to api/blogs
by adding the code block below to your handlers
array. This endpoint should return all the blogs:
rest.get("/api/blogs", (req, res, ctx) => {
return res(ctx.json(blogs));
}),
We called the get
method on rest
in the code block above. This method takes two arguments, a path and a response resolver. A response resolver is a callback function that contains all the logic required to resolve a request. It takes three arguments, request
(req
), response
(res
), and context
(ctx
).
request
contains all the details about the current request.
response
is used to construct the mock response.
context
contains request-specific utility functions that help build the mock response.
Then we return the blogs
array as JSON to the client by turning it into JSON using the ctx.json
method and wrapping it in res
.
Next, create a mock route handler for a GET request to api/blogs/:id
by adding the code block below to your handlers
array:
rest.get("/api/blogs/:id", (req, res, ctx) => {
//Extracting id from URL
const id = req.params.id;
//Getting blog from db (array)
const blog = blogs.find((blog) => blog.id === id);
//Sending response
if (blog) {
return res(ctx.json(blog));
} else {
return res(ctx.json({ message: "Blog not found" }));
}
}),
This endpoint should return a single blog post.
Next, create a mock route handler for a POST request to api/blogs/new
by adding the code block below to your handlers
array.
rest.post("/api/blogs/new", async (req, res, ctx) => {
//Getting request body in json
const blog = await req.json();
//Assigning ID
blog.id = blogs.length + 1;
//Adding blog to db (array)
blogs.push(blog);
//Sending response and setting status code
return res(
ctx.status(201),
ctx.json({
id: blogs.length + 1,
blog,
})
);
}),
This endpoint should return and add a new blog to the existing list.
Next, create a mock route handler for a DELETE request to api/blogs/:id
by adding the code block below to your handlers
array:
rest.delete("api/blogs/:id", (req, res, ctx) => {
const id = req.params.id;
const blogIndex = blogs.findIndex((blog) => blog.id === id);
//Removing blog from db (array)
blogs.splice(blogIndex, 1);
return res(ctx.json({ message: "Deleted successfully" }));
}),
This endpoint should delete the specified blog post.
Next, create a mock route handler for a PATCH request to api/blogs/:id
by adding the code block below to your handlers
array:
rest.patch("api/blogs/:id", async (req, res, ctx) => {
const id = req.params.id;
const reqBody = await req.json();
const blog = blogs.find((blog) => blog.id === id);
//Updating blog
blog.title = reqBody.title;
blog.body = reqBody.body;
//Sending response
return res(ctx.json({ message: "Updated successfully" }));
}),
This endpoint should edit the specified blog post.
Your finished handlers array should look like the code block below:
export const handlers = [
rest.get("/api/blogs", (req, res, ctx) => {
return res(ctx.json(blogs));
}),
rest.get("/api/blogs/:id", (req, res, ctx) => {
//Extracting id from URL
const id = req.params.id;
//Getting blog from db (array)
const blog = blogs.find((blog) => blog.id === id);
//Sending response
if (blog) {
return res(ctx.json(blog));
} else {
return res(ctx.json({ message: "Blog not found" }));
}
}),
rest.post("/api/blogs/new", async (req, res, ctx) => {
//Getting request body in json
const blog = await req.json();
//Creating unique id
currentId += 1;
//Assigning ID
blog.id = currentId;
//Adding blog to db (array)
blogs.push(blog);
//Sending response and setting status code
return res(
ctx.status(201),
ctx.json({
id: currentId,
blog,
})
);
}),
rest.delete("api/blogs/:id", (req, res, ctx) => {
const id = req.params.id;
const blogIndex = blogs.findIndex((blog) => blog.id === id);
//Removing blog from db (array)
blogs.splice(blogIndex, 1);
return res(ctx.json({ message: "Deleted successfully" }));
}),
rest.patch("api/blogs/:id", async (req, res, ctx) => {
const id = req.params.id;
const reqBody = await req.json();
const blog = blogs.find((blog) => blog.id === id);
//Updating blog
blog.title = reqBody.title;
blog.body = reqBody.body;
//Sending response
return res(ctx.json({ message: "Updated successfully" }));
}),
];
Session Replay for Developers
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.
Integrating MSW with your Application (Browser)
After creating the mock route handlers for your application, you will need to integrate MSW into your application by creating a service worker that will intercept the requests made by your application.
Note that you won’t need to write any service worker code yourself. Instead, you will use a dedicated MSW CLI to generate it.
Run the command below to generate the required service worker files:
npx msw init public/ --save
Note: If you are not working with Create React App, replace “public/” with the relative path to your server’s public directory.
Next, in your browser.js
file, import setupWorker
from msw
and your handlers
array. Like so:
//browser.js
import { setupWorker } from "msw";
import { handlers } from "./handlers";
Then, create and export a variable, worker
, and initialize it by calling the setupWorker
method and passing a de-structured handlers
array as an argument.
Like so:
//browser.js
export const worker = setupWorker(...handlers);
Finally, conditionally import worker
from your browser.js
file, and activate it by calling the start
method. Like so:
if (process.env.NODE_ENV === "development") {
const { worker } = require("./mocks/browser");
worker.start();
}
Once your service worker is enabled, “[MSW] Mocking enabled” should be displayed on your console as shown in the image below:
Conclusion
This tutorial covered the basics of mocking an API server using the MSW package. You can take your API mocking to a different level by implementing a mock database using the msw/data
package, which you can use to mimic an actual database.