Network mocking
Network Mocking (🏁 solution)
Legacy discount scenario
I will start with the legacy discount code scenario.
The intention behind this functionality is to display a warning for the user if they have applied a legacy discount code. How do I know if a code is legacy or not? I don't. That's for the server to figure out. My concern is making sure my
<DiscountCodeForm /> component can handle such a response from the server.I don't want to treat all server responses as those describing a legacy discount code. Instead, I want to create an override that makes it true only for a particular test.
First, I access the
worker fixture in the argument to the relevant test():test('displays a warning for legacy discount codes', async ({
// Access the `worker` fixture we've prepared earlier.
worker,
}) => {})
Next, I will create a new request handler matching the same
POST request that sends the discount code to the server, but this time, I will handle it differently:test('displays a warning for legacy discount codes', async ({ worker }) => {
worker.use(
http.post<never, string, Discount>(
'https://api.example.com/discount/code',
async ({ request }) => {
const code = await request.text()
return HttpResponse.json({
code,
amount: 10,
isLegacy: true,
})
},
),
)
render(<DiscountCodeForm />)
Here, I am using a request handler override by giving a new set of request handlers toworker.use(). These overrides will take priority over the happy-path handlers I have insrc/mocks/handlers.ts.
Only in the context of this single test, any submitted discount codes will receive
isLegacy: true in the server response.But the only way to verify that it works is to add some assertions.
I will add the first assertion around the discount code being applied, since legacy discounts still are:
await expect.element(page.getByText('Discount: LEGA2000 (-10%)')).toBeVisible()
And, finally, one remaining assertion for the warning message regarding the use of a legacy code:
await expect
.element(page.getByRole('alert'))
.toHaveTextContent('"LEGA2000" is a legacy code. Discount amount halved.')
🦉 Since the legacy code warning is a<p>text element that doesn't have an accessible name, I am relying on the.toHaveTextContent()matcher to both narrow down the locator and assert on the message displayed to the user.
Error response scenario
Reproducing an error response from the server will follow similar steps. The only things that will differ are the mocked response I will use and the assertions the test will have.
In my request handler override in the error test case, I will respond with a 500 response:
test('displays an error when fetching the discount fails', async ({
worker,
}) => {
worker.use(
http.post('https://api.example.com/discount/code', () => {
return new HttpResponse(null, { status: 500 })
}),
)
render(<DiscountCodeForm />)
This makes any attempts to apply a discount code in this test to be met with a server error. Since that's the case, I can model my expectations accordingly once the form is submitted.
await expect
.element(page.getByRole('alert'))
.toHaveTextContent('Failed to apply the discount code')
Here, I am expecting this message to alert the user that applying the discount failed. The
role="alert" on the message will announce this element for the user so they aren't left confused what happened to their discount.Reusing the MSW setup
One of the core benefits of using MSW is that you can reuse your request handlers across multiple environments and tools. For example, here are the steps to reuse the same browser integration for local development.
To enable MSW for development in the browser, go to
and create a new function called
enableMocking:import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import { App } from './app.jsx'
async function enableMocking() {
if (process.env.NODE_ENV === 'development') {
const { worker } = await import('./mocks/browser')
await worker.start()
}
}
enableMocking().then(() => {
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
})
enableMocking() function to make sure that MSW is ready to handle the network before rendering your application.This functions does a couple of things:
- Imports the
./mocks/browser.tsmodule conditionally to enable MSW only in development; - Calls
await worker.start()to actually register and start the Service Worker.