One thing that has really changed my way of building with React is implementing tests much earlier in the development of a feature. I tend to feel almost claustrophobic working on a single laptop screen and flipping back and forth between my text editor and a browser window to see changes – this means most of my frontend development is relegated to my office desk with an ultrawide monitor. I think this is also a big reason why I like building APIs and command line scripts since I feel free to build them “everywhere” and can keep everything on one screen.
If you feel the same way and aren’t testing frontend components – now is the time to start! It took an embarrassingly long time to click that I can run jest (now pretty much always vitest) and get some immediate feedback on functionality all in one screen. I still save layout and styling for the bigger monitor but this opens up where I can work on frontend projects significantly. It’s also super satisfying see that total number of tests grow over time in a project in my CI pipelines.
Integration tests with React also took a while to click – until I looked into using Mock Service Worker (MSW) more effectively. I like to structure my projects with a “routes” directory which contains Page components. These will call APIs as needed and pass that data to the actual layout components once received. My initial use of MSW wasn’t much more than mocking “fetch” and didn’t really give too much value while at the same time being pretty brittle. That meant it wasn’t used too effectively – until I actually started reading the docs(!)
“Avoid request assertions”
This mindset of testing how a component reacts to different data being presented meant I could write tests more like an e2e test and less like unit tests – a huge improvement. I still unit test backend business logic extensively but I haven’t run into an issue yet where I’m stuck on the frontend trying to diagnose a bug or have a regression that one of these or the e2e suite doesn’t catch. Coming from a QA background (in manufacturing mind you) I’m very willing to quantify and accept various risk in my management strategy – my frontends aren’t going to kill anyone or lose any money.
My current method for using MSW is pretty much from the docs – I create an array of handlers and set up the server in the vitest config file. The one thing that has changed everything was actually using the request object to let the test communicate what data it would like back from the server. I could probably get much more granular with the actual request bodies but for each handler I pick one field (like email for a registration route) and use that to control the response:
import { beforeAll, afterAll, afterEach } from 'vitest'
import { cleanup } from '@testing-library/react'
import '@testing-library/jest-dom/vitest'
import { setupServer } from 'msw/node'
import { HttpResponse, http } from 'msw'
const handlers = [
http.post("/api/v1/user/create/", async ({ request }) => {
const body = await request.json()
if (body.email === "[email protected]") {
return HttpResponse.json({
message: "User successfully created"
}, {status: 201})
} else if (body.email === "[email protected]") {
return HttpResponse.json({
message: "Something went wrong"
}, {status: 400})
}
}),
]
const server = setupServer(...handlers);
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterAll(() => server.close())
afterEach(() => {
cleanup();
server.resetHandlers();
})
I like this because it’s very easy to hop back in to the setup file and add a new type of response. Using basic fields for control means while testing I just have to remember something like “gooduser” vs “baduser” and I can expect the right data to come back from the server. I’ve seen a couple code generation tools for generating this right off the openapi spec from the server, but I have a love/hate relationship with code generation and haven’t found a need to get into that yet.
This whole post doesn’t have too much “new” stuff beyond the excellent MSW docs but should hopefully serve as a checkpoint I can look back on in the future and either be like “wow that was great” or “what was past Joe thinking…”
I think I know which way that one will go!