Mock Service Worker — In the Browser

Jack Bittiner
Cazoo Technology Blog
7 min readApr 16, 2021

--

Have you ever wanted to quickly build a front-end prototype that looks and feels like a finished product?

Do you have a tough time reproducing pesky production bugs locally?

Are you tired of waiting for the API to be ready?

Well gather round and let me show you a package that’s going to put an end to your development woes!

Mock Service Worker (MSW) is an API mocking library and it’s pretty damn awesome. It may have a typically boring techy web name but don’t be deterred. I have been using it in my most recent projects at Cazoo and it’s worked wonders and really helped with development.

For the purpose of this blog I will focus on using MSW in the browser and how it can help you build and maintain applications.

What is MSW?

Tl;dr — it mocks stuff.

Like I said above, MSW is an API mocking library. What is that? Well, an API mocking library should imitate a real server and provide realistic API responses to requests.

It registers a service worker, and this worker intercepts HTTP requests and returns whatever mocked response you desire.

So first of all…. What is a service worker? Good question, I didn’t really know either.

A service worker is essentially a JavaScript file that runs separately from the main browser thread, intercepting network requests, caching or retrieving resources from the cache, and delivering push messages. (Stolen directly from Google. Thank you Google!)

MSW utilizes the Service Worker API that intercepts requests for the purpose of caching, and responds to requests with your mock definition on the network level.

So like I said at the start — it mocks stuff.

Why use MSW?

There are a good number of reasons to use MSW in the browser but main ones I’ll focus on are:

  1. Development
  2. Debugging
  3. Experimentation

I’ll dive into more details on how we can aid these processes once we have a project set up.

Setup

The installation is very quick and will only take a few minutes, whether in a new or existing project.

But lets run through the steps:

First let’s create a new react app.

npx create-react-app my-really-cool-mocked-app  cd my-really-cool-mocked-app

Now let’s install MSW.

yarn add msw --dev

We need to initialise MSW in our public directory

npx msw init public/

If you look inside your public directory you should now see a mockServiceWorker.js. Wow, wasn’t that easy?

Now let’s get our worker all wired up!

First let’s make a mocks directory with a couple files we’re going to need.

mkdir ./src/mocks 
touch ./src/mocks/browser.js
touch ./src/mocks/handlers.js

Inside browser.js file, put in the following code snippet:

import { setupWorker } from "msw"; 
import { handlers } from "./handlers";
export const worker = setupWorker(...handlers);

Then in the handlers.js file let’s just start with an empty array of handlers

export const handlers = [];

We can build on this later.

The final thing we want to do now is to register the service worker. Inside your index.js file add the following code:

if (process.env.NODE_ENV === "development") {   
const { worker } = require("./mocks/browser");
worker.start();
}

This will start the service worker. But only in the development environment. Your production build will be unaffected by mocking.

This will start the service worker. But only in the development environment. Your production build will be unaffected by mocking.

yarn start

Open the console and you should see the following

Niiiiiiice, everything is coming up Milhouse! (Obligatory tech blog gif)

The First Handler

I suppose we should fetch something and post something shouldn’t we? Go on then, let’s see what we can do.

Let’s fetch some cars and display them in a list (we are a car retailer after all). We want to hit an endpoint called /api/get-some-cars

Inside the handlers.js file lets mock our first endpoint.

import { rest } from "msw";export const handlers = [
rest.get("/api/get-some-cars", (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json([
{ regPlate: "A B19 GUY", make: "Audi ", model: "TT" },
{ regPlate: "ZR0 K1DZ", make: "Land Rover", model: "Defender" },
{ regPlate: "1M A CAR", make: "Ford", model: "Ka" },
{ regPlate: "5IMP50N5", make: "Powell", model: "The Homer" },
])
);
}),
];

What this means is that whenever our app tries to hit the /api/get-some-cars endpoint it will return that JSON blob.

Let’s write some code in our App.js file to do a HTTP request to this endpoint and display this data. You can do it any way you want. You can use any fetching library, it does not matter one bit.

Below is my quick thing just to get it working:

import "./App.css";
import React, { useEffect, useState } from "react";
function App() {
const [cars, setCars] = useState([]);
useEffect(() => {
(async () => {
const response = await fetch("/api/get-some-cars");
const json = await response.json();
setCars(json);
})();
}, []);
if (cars) {
return cars.map(car => {
return (
<p key={car.regPlate}>
Reg Plate: {car.regPlate} - Make: {car.make} - Model: {car.model}
</p>
);
});
}
return <div className="App"></div>;
}
export default App;

Now if you open this in the browser you should see:

Wow…. isn’t that beautiful??? Yes. Yes it is!

You can also see the request in the network tab, which is pretty cool.

Now for a sense of development completion, let’s send off a post request. Let’s say each vehicle has a button that, when you clicked, sends off a request to say that the vehicle was destroyed.

We need a new handler. It will look something like this:

rest.post("/api/destory", (req, res, ctx) => {
return res(ctx.status(200));
})

Now in our app let’s add a button which sends the post request. Something like this:

import "./App.css";
import React, { useEffect, useState } from "react";
function App() {
const [cars, setCars] = useState([]);
useEffect(() => {
(async () => {
const response = await fetch("/api/get-some-cars");
const json = await response.json();
setCars(json);
})();
}, []);
if (cars) {
return cars.map(({ regPlate, make, model }) => {
return (
<>
<p key={regPlate}>
Reg Plate: {regPlate} - Make: {make} - Model: {model}
</p>
<button
onClick={async () => {
await fetch("/api/destroy", {
method: "POST",
body: JSON.stringify({
regPlate,
make,
model,
}),
});
}}
>
destroy me
</button>
</>
);
});
}
return <div className="App"></div>;
}
export default App;

Now we have something like the following:

And if we click on the first button we should be able to see our request payload in the network tab:

This is everything you need to do to get set up.

Obviously there is more complicated stuff that can be done but for the purpose of this blog I think that is enough.

Real life use

  1. Development — You’ve just seen it. We made a super basic app only using mocking. If there is no backend yet, or it’s under development, you can still build the front end. Or if you just want to quickly throw together a prototype to share with users, you can speed through it with just mocks. And once you have something that looks like what you want, you can design the API to return what your mocked handlers return.
  2. Debugging — A lot of bugs happen in the front end due to strange data issues. Here is a trick that has helped me lots. If there is a bug in production, copy and paste the data and put in into your mock. Now you should be able to easily step through and debug the issue.
  3. Experimentation — change the API response to anything you want it to be and see how your app would work. If it’s amazing then develop the backend.

Conclusion

I fully recommend giving MSW a try. It’s so quick to set up, you have nothing to lose.

Just this basic setup has sped up delivery in the projects that I have worked on. There are more complex and interesting things you can do too, but i’ll leave you to figure that out.

I will follow this blog up with how to set up and run MSW in node for your tests. This is another really helpful feature of MSW where you can use the same handlers in your unit/E2E/integration tests and have full confidence in your application.

MSW is something that I discovered during my time at Cazoo. The tech culture here is fantastic and it gives me the time and space to explore new packages like MSW. Please take a look at our job board to see if there’s an opportunity for you!

Thanks for reading! JB

--

--