For web applications, we champion the testing trophy approach popularized by Kent C. Dodds. Unlike the classic testing pyramid, this approach places the focus on writing a lot of integration tests rather than on writing a lot of unit tests.
___________
/ \ /\
/ E2E \ / \
/---------------\ / \
/ \ / \
/ \ / E2E \
/ INTEGRATION \ /----------\
\ / vs. / \
\ / / \
\-----------------/ / INTEGRATION \
\ UNIT / /------------------\
\-------------/ / \
/ \ / \
/ STATIC \ / UNIT \
/ \ / \
/___________________\ /____________________________\
For the details, please see the full article. But the gist is instead of focusing on achieving 100% code coverage, we aim to achieve highest possible code confidence and return of investment. Writing a lot of unit tests does help boost confidence (though only at micro scale), but also puts pressure on the cost of changes - almost any changes would break some tests. As such, focusing on how the app works on the larger scale such as at integration level and beyond, would allow us to develop with more flexibility while ensuring that what we changed/added do not break the existing feature set.
In the
trophyparadigm,staticrefers to a linting and type-checking which gives a huge confidence boosting during development and helps prevent bugs withundefinedvalue.
Also, just a note why end-to-end (functional) testing just takes a small portion in both diagrams? That's because end-to-end tests, despite being super powerful, are usually costly to setup and maintain.
See detailed comparison between different testing levels here
static: We use ESLint for linting and Typescript for type-checking.
unit: We should really only write unit tests for utilities and helpers. We use Jest as the testing framework.
integration: We should have integration tests to cover all use cases. We don't make real network call here, so we should mock out our network client (or not (?), see TODO). We also use Jest.
e2e: Almost the same as integration test, except we do make real network call and interact with the backend. We would like to use Playwright. In a way, e2e tests are valuable here because it can be used to test the whole application stack as well as a demo for usage and features of the app.
When writing tests for React components, prefer queries that reflect how a user perceives the UI:
- Prefer
getByRole,getByLabelText,getByText, andgetByTitleovercontainer.querySelector(...). - Avoid selecting elements by CSS class names. Class names are implementation details and change frequently during refactors.
- If there’s no good user-facing query (e.g., purely decorative elements), it’s OK to fall back to DOM selectors, but keep this to a minimum.
- Use
data-testidsparingly as an escape hatch when no accessible query is practical.
- Mock server request instead of mocking the method that makes the network call, maybe this is the better way to write integration test (or even e2e tests)? https://kentcdodds.com/blog/stop-mocking-fetch?ck_subscriber_id=564011516 https://github.com/mswjs/msw