-
Notifications
You must be signed in to change notification settings - Fork 25
Unit tests
We use Testing Library for UI unit tests.
It gets a component and renders it in a sandbox environment (click here to see why this approach). Then we can assert the rendered DOM using snapshots or query helpers.
There's a catch though. As we know components can use hooks, some of the hooks are asynchronous. This means the component will render several times before getting to a stable state. In the browser React updates the page with every re-render cycle. In a test, we need to tell explicitly "hey, wait for another cycle and then continue". The way to do that is to call the act
function (if interested, you could start from FAQ "How do I fix 'an update was not wrapped in act(...)' warnings?"), use async utilities, or await for our waitForEffect helper.
Some rules of thumb:
- try to have one
expect
per test; - use
test.each
for multiple test cases of the same functionality (you can have the test name, value to test and expected value in the array); - use contextual matchers instead of generics, favor
.toBeNull
over.toBe(null)
, use HTML element matches like.toHaveClass
, use.not
to negate the matcher like.not.toBeNull
.
Snapshot testing means that the test will render a component save the rendered HTML in a snapshot file and on the next run it will compare the freshly rendered HTML with the snapshot. Snapshot tests are simple to create but may be cumbersome to maintain. Keep in mind that when you add a snapshot test for a page, a change in any component used on that page will fail the test. Snapshot tests are good as unit tests for small components (like SwitchButtonWidget) or as integration tests for bigger components involving several moving parts.
See examples:
The test still renders the component but instead of comparing the markup to the saved snapshot, the test expects to find (or not to find) certain elements or text content in the rendered DOM. To access the rendered DOM you can use Queries (preferable) or element query selectors. Trigger user events like clicks or text input as needed (FormEditor has a lot of examples).
See examples:
Just regular unit testing.
See examples:
Approach #1: split the component into a presentational (sometimes call "dumb") component and a stateful component (sometimes called a "smart" or "connected" component)
Approach #2: split the useAsyncState value generators/factories out into a helper method. Use jest mocks to mock the return value of the factory using mockResolvedValue
. If a component has multiple useAsyncState uses, you may want to combine them into a single useAsyncState call which returns a single promise (just be sure to use Promise.all where appropriate to enable network request promises to run concurrently)
If you need to test a component that requires Router context, you can wrap the component under test with the StaticRouter.
import { StaticRouter } from "react-router-dom";
...
<StaticRouter>
<InstalledPage extensions={[]} push={jest.fn()} onRemove={jest.fn()} />
</StaticRouter>
Example: InstalledPage.test
Add <Formik>
around your component. You can use createFormikTemplate
helper from formHelpers
const FormikTemplate = createFormikTemplate({
[RJSF_SCHEMA_PROPERTY_NAME]: {
schema,
uiSchema: {},
} as RJSFSchema,
});
render(
<FormikTemplate>
<FormEditor activeField={fieldName} {...defaultProps} />
</FormikTemplate>
);
Example: FormEditor.test