Testing!
Components
What do we test?
Given a set of initial states, does the component exhibit the appropriate side-effects/behavior
Should be minimal, and separated from complex hook behavior
Given a set of initial states, does the component render the appropriate output
Limited exclusively to the code logic in the associated component file
Why?
Components are our View part of the UI. the tests of these component should solely focus on making sure that they present the right views in the appropriate circumstances
How?
Separate complex hook logic into custom hook, and mock that hook when testing the component
Mock all sub-components
Take snapshots of the component in different states (incoming props and hook output), and inspect those snapshots for relevant details.
Hooks
What do we test?
Given a set of initial states, does the hook exhibit the appropriate side-effects/behavior
Given a set of initial states, does the hook return the appropriate output
Limited exclusively to the code logic in the associated component file
Why?
Hooks are our interaction and glue step of our UI. They gather data from the model and communicate it to components, as well as configuring their own behaviors that can be associated with those components. Testing these behaviors in isolation allows for thorough examination of the behaviors without having to mix it with simulating component states.
How?
Mock all react hooks to have testable output
use keyed state (from react-unit-test-utils) to validate state usage.
Asynchronous events
What do we test?
Given a successful return of valid data, does the expected follow-on behavior occur
Given a failure of the asynchronous event, does the expected follow-on behavior occur
Why?
For network/async events, we want to be able to cleanly simulate success and failure without relying on the external resource that the request would be communicating with.
How?
Mock functions that return Promises to return
Promise.resolve(validData)
orPromise.reject(error)
If necessary for complex systems, you can instead return custom promises that export their resolve/reject logic to be controlled externally
const promiseCallbacks = {}; const myPromise = new Promise((resolve, reject) => { promiseCallbacks.resolve = resolve; promiseCallbacks.reject = reject; })