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 to worker.use(). These overrides will take priority over the happy-path handlers I have in src/mocks/handlers.ts.
Only in the context of this single test, any submitted discount codes will receive isLegacy: true in the server response.
Notice how you don't have to leak any business logic into this mock to test this network-related scenario. Prefer scoped test cases that run in a clearly defined context.
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
src/main.tsx
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>,
	)
})
Wrap the initialization of your app in the enableMocking() function to make sure that MSW is ready to handle the network before rendering your application.
This functions does a couple of things:
  1. Imports the ./mocks/browser.ts module conditionally to enable MSW only in development;
  2. Calls await worker.start() to actually register and start the Service Worker.

Please set the playground first

Loading "Network mocking"
Loading "Network mocking"