E2E Mocking
Loading "Intro to E2e Mocking"
Run locally for transcripts
When testing things, sometimes you need to fake things out. Imagine you sell
plush Koalas, and you want to test the checkout flow. If you were to actually
make a request to your payment processor, you'd be charged every time you ran
your tests. While it would be fun to have hundreds of Koalas in your house,
you'd probably go broke. ๐
It's a good idea to fake out certain things when testing. Especially third
parties. And this isn't just a good idea for testing, it's also a good idea for
development as well.
Personally, I prefer to be able to do all my work completely offline, so I mock
out all third-party requests during development as well as in testing. This
helps a great deal with my productivity.
That said...
Whenever you make a fake version of something during testing, you're losing
confidence!
Make certain there's a legitimate benefit for that cost of confidence. Making a
fake version of something in testing is called "mocking" and is often necessary,
but goes against the principle of ensuring your tests resemble the way the
software is used.
Read more about this in
The Merits of Mocking.
Another thing you want to watch out for, though, is to make sure you don't impact
your source code to make it easier to mock. This is why it's great to have a
tool capable of unobtrusively intercepting HTTP requests made by your app. And
that tool is called MSW (short for "Mock Service Worker").
MSW originated as a utility for mocking browser requests but is more often used
in Node as a testing utility. In Remix apps, it's pretty much only used on the
server during testing and development because, typically, most (if not all)
external network requests are made on the server.
๐ You'll find
the docs for server setup here. Here's
a quick example, modified from the docs:
import { rest, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'
import closeWithGrace from 'close-with-grace'
const { json } = HttpResponse
const server = setupServer(
// Describe the requests to mock.
http.get('/book/:bookId', () => {
return json({
title: 'Lord of the Rings',
author: 'J. R. R. Tolkien',
})
}),
)
server.listen({ onUnhandledRequest: 'warn' })
if (!process.env.VITEST_POOL_ID) {
console.info('๐ถ Mock server installed')
}
closeWithGrace(() => {
server.close()
})
In general, it's best to have the default HTTP mocks handle the happy path,
though I do recommend you try to make them as similar to the real deal as
reasonable. I often use
faker
to generate fake data for what's returned from
the APIs and even do basic validation on the requests as well (make sure the
Authorization
header is present and the request body follows the schema, etc).With Playwright
One of the tricky things we run into when running tests against our app in
Playwright is that we can't easily communicate between the playwright server and
our app server because they are running in different processes. The best way
I've found to communicate is through the file system. However, this is a bit
limited, which is one of the reasons I typically focus E2E tests on happy path
user flows and edge cases are handled by integration tests with vitest.
When your tests and source code run in the same process, you can take advantage
of MSW's
server.use
function to
alter the default handlers, which allows you to test situations where the HTTP
request fails or returns an error.MSW is currently working on a way to improve process-to-process communication
for this use case and it's really pretty cool. It will allow us to have much
more sophisticated HTTP mocks (using
server.use
, which we'll do later in the
workshop with vitest).Even with that, writing the emails to the file system has some benefits (like
being able to manually inspect the emails that were sent).