[Unit testing] Mocking the Network with Mock Service Worker
Limitations of Mocking the Store
In the last lesson, we successfully mounted our EventList component and provided initial state to our Vuex store. While this allowed us to validate that our EventCards were rendering, it bypassed the Vuex store’s own logic to call through to axios. In other words, we put off the work to create testing infrastructure necessary to mock network requests.
We had good reason to do this, though. Using a real Vuex store is the easiest way of mounting any component that depends on Vuex or another state management library.
However, our next task is to validate that when the EventList fails to fetch events, it shows an error message.
For that, we’ll need to execute the callback within EventList’s created hook, which requires use to make a failing axios request inside of EventService.
Should you mock axios?
If you trace through the call to fetchEvents
, you’ll see that it depends on the EventService and uses an axios client to get all of the events.
fetchEvents
returns a promise, and if axios fails to respond successfully, the EventList will handle the error. So far, we don’t have a pattern that will allow us to trigger a failure response from axios.
There are two common solutions:
- Use file-based module mocking (mocking axios)
- Use environmental network mocks (mocking API requests)
File-based Module Mocking
File-based module mocking is a technique popularized in Jest. It can be very powerful, but also very brittle because it’s mocking the literal file names and module names in your system. Most other test runners, like Mocha, do not support this style of mocking.
By using file-based module mocking with our EventService or even axios, we would be able to mock out the implementation of axios with custom requests/responses every time axios.get was invoked. This is an extremely common approach to testing code that depends on network requests and we cover it in our Introduction to Unit Testing course.
Environmental network mocks
On the other hand, environmental network mocks are a technique focused on making the runtime environment respond as if there were a live server available for your source code to execute against. Tools like Mock Service Worker and Cypress’s cy.intercept() command allow your code to execute as if a real server were available. The benefit of this strategy is that your code does not behave differently under tests, and your tests are not coupled to a particular library or method of fetching data. Your tests will only be tied to your applications’ API contract.
What is Mock Service Worker?
Let’s start with the basics.
Mock Service Worker is a library that helps you test, develop, and debug the parts of your application that make network requests. It works by intercepting requests on the network level and allows you to reuse the same mock definition for testing, development, and debugging.
Benefits of Mock Service Worker
Because Mock Service Worker operates at the network-level, it’s agnostic to how your client fetches data. This means that if you were to switch from XMLHttpRequest
to fetch
to axios
, your tests wouldn’t have to change.
As an aside, a good sign your tests are well-written is when you’re able to make major refactors to your codebase without needing to touch your tests. For this reason, using high-level mocks, like Mock Service Worker, that operate at an environmental level are preferred to using lower-level mocks that operate at a source code level.
You don’t expect your customers to mock
fetch
do you? So don’t expect your tests either. Target any state of your API while testing your application exactly how your users interact with it. - mswjs.io documentation
Configure Mock Service Worker
- Initial Setup
- Running Mock Service Worker in Jest
1. Initial Setup
First, let’s run npm install msw -D.
Next, create a src/mocks directory where we’ll place all of our mock definitions and handlers.
Within that directory, create a handlers.js file.
We’re using REST in this application, so let’s import that API from the msw package.
src/mocks/handlers.js
import { rest } from 'msw'
Next, we’ll define our first handler.
The Mock Service Worker handler API is very similar to the API you’d see when writing an Express-style server. You have access to a request object, a response, and a shared context. We won’t go into those too deeply right now.
All we need to do is respond with a status code (we’ll do that using the context.status method), and then define a response body (we’ll do that with the context.json method). We’ll invoke the response method with those two arguments and return out of the handler.
Don’t worry about how the data looks. After we validate that our testing infrastructure works, we’ll refactor our mock data.
src/mocks/handlers.js
import { rest } from 'msw'
export const handlers = [
// Handles the GET /events request
rest.get('/events', (req, res, ctx) => {
const data = { message: 'Hello from Vue Mastery!' }
return res(ctx.status(200), ctx.json(data))
})
]
Great! Now let’s setup our server in Jest so we can test if it correctly handles the API route we’ve mocked.
2. Running Mock Service Worker in Jest
Now, let’s create a server.js file within src/mocks, which will serve as the entry point when we use Mock Service Worker within our tests.
First, we’ll import setupServer from the msw/node entry point.
Next, we’ll import the handlers we created in the step above.
Finally, we’ll spread the handlers into setupServer **and export the result.
src/mocks/server.js
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
// This configures a request mocking server with the given request handlers.
export const server = setupServer(...handlers)
Setup File
Up until now, we haven’t had to do any global setup or teardown for each test, or any one-time setup for our test runs. Most test runners provide a setup file for this. There is no default in Jest and you must explicitly configure it within the jest.config.js file.
jest.config.js
module.exports = {
// ...
setupFilesAfterEnv: ['<rootDir>/tests/unit/setup.js']
}
Next, create the setup.js file within the tests/unit directory. You can leave it empty for now.
Let’s quickly make sure we haven’t broken anything by running npm run test:unit
.
Making infrastructure changes safely and sanely
When making testing infrastructure changes, it’s a good idea to test the test infrastructure using simple examples instead of your own source code.
Let’s quickly make a spec called tests/unit/smoke.spec.js and add a smoke test to it.
tests/unit/smoke.spec.js
it('works', () => {
expect(true).toBeTruthy()
})
Now, run only this spec in watch mode.
#### INTERNAL NOTE: Vue CLI Service isn't properly keeping the jest watch task alive.
#### Need to do this instead of `npm run test:unit smoke --watch`
npx jest smoke --watch # shorthand for smoke.spec.js
Super. Now we know if we break our tests/unit/setup file that it’s because of the Mock Service Worker code and not our own application code.
We’ll keep this watch task up until we’re certain that our mock server is working correctly. If there are any issues in our setup.js, handlers.js, or server.js files, our smoke.spec.js will fail.
Starting the Server
Now open up the tests/unit/setup.js file and import the mock server.
// tests/unit/setup.js
import { server } from '../../src/mocks/server'
This will cause the mocks/server.js file to be invoked and create a server instance; however, the server isn’t listening or receiving any requests yet.
Before each test, we’ll need to call server.listen() with a port to begin receiving API requests. And then reset the handlers after each test with afterEach
. Finally, close the server after all the tests are done running with afterAll
:
tests/unit/setup.js
import { server } from '../../src/mocks/server'
beforeAll(() => {
server.listen(3000)
})
afterEach(() => {
server.resetHandlers()
})
afterAll(() => {
server.close()
})
Now let’s go ahead and test out the handler we wrote earlier by making a network request to /events
.
tests/unit/smoke.spec.js
import axios from 'axios'
it('works', async () => {
const result = await axios.get('/events')
// "{ message: 'Hello from Vue Mastery!' }"
console.log(result.data)
expect(true).toBeTruthy()
})
We should see “Hello from Vue Mastery!” print out in the console of our watched smoke test.
Awesome! Mock Service Worker is working within our spec and our testing infrastructure.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
2024-02-26 [Rust] Working with enums by pattern matching variants
2024-02-26 [Javascript] Macrotask vs Microtask
2024-02-26 [Go Unit testing] Unit testing Go program
2024-02-26 [Rust] impl TryFrom and try_into()
2024-02-26 [Rust] String vs String slice
2024-02-26 [Rust] Struct method syntax
2024-02-26 [Rust] Struct Update Syntax