Testing
Testing proves integrity and provides the ability to:
- Verify intent by exercising code it in various ways
- Provide confidence that is existing functionality still works when refactoring or adding new requirements
Write tests, not too many, mostly integration - Kent Dodds
Leverage best possible tools for testing stack to deliver quality products.
Strategy
Adopt two mindsets
- End User Experience - Input and Expected Output
- Developer Experience - Function
Don't be obsessed with code coverage. Safe guard the user from taking the unhappy path
Priority | Type | Notes |
---|---|---|
1. High value/risk features | E2E | Signup, Login, Payment, etc. |
2. Edge Cases in high value features | Integration/Unit | |
3. Things that are easy to break | Integration/Unit | |
4. Component interaction testing | Integration/Unit |
Concepts
Being DRY in tests is bad for your
Integration Testing
Mocking
Mocking should be avoided if at all possible. It is better to combine two or more components in an integration test and interact with the components as the user would.
Use Mock Service Worker to mock APIs at the network level rather than excessively mocking at the component or service layer
Test Fixtures
- Loading a database with a specific, known set of data
- Erasing a hard disk and installing a known clean operating system installation
- Copying a specific known set of files
- Preparation of input data and set-up/creation of fake or mock objects
Frontend
The more your tests resemble the way your software is used, the more confidence they can give you.
Design: Business logic exists in pure functions not than UI components. For example, a Shopping Cart UI component should not compute the cart total. As much possible push business logic to the Backend API.
Flow
Use Component Driven Development to save time and money.
Start from the most nested component, then come back to the root writing down interactions and expected outcomes.
📚 Isolate components using Storybook. Write test cases where each state is reproduced using props and mock data. ✅ Catch visual bugs and verify composition using Chromatic. 🐙 Verify interactions with Jest and Testing Library. ♿️ Audit accessibility of your components using Axe. 🔄 Verify user flows by writing end-to-end tests with Cypress. 🚥 Catch regressions by automatically running tests with GitHub Actions.
Flow | Notes |
---|---|
1. User interactions | |
2. Conditional rendering | |
3. Utils/Hooks |
Foundations
Setting up a test environment.
Approach
Snapshot vs traditional unit testing: Snapshot tests lack the expression of this intent. So for anything beyond the simplest of components, prefer traditional unit tests.
Procedure
Difference between queryBy, getBy and findBy queries Checking for existence of an element Waiting for removal of an element Waiting for something to happen fireEvent() vs userEvent Mocking an event handler Avoid mocking by using Mock Service Worker Overriding MSW handlers Testing page navigation Suppressing console errors
Backend
Liberal on what you accept; mean on what you let pass
Blockchain
Testing is of critical importance to blockchain development.
Typescript Code
Heavily test type predicates. That will increase your confidence in the predicates, which will allow you to trust the type system as a whole.
Test function overloads to ensure the body is returning expected types. It's probably best to avoid them unless they solve a serious problem in your system.