Testing Library
Purpose
You want to write maintainable tests that give you high confidence that your components are working for your users. There are many open-source testing libraries designed for React and React-like frameworks. The most popular one of them is the testing library that provides friendly and universal APIs to write scalable test cases.
We provides a binding for the testing library called @goji/testing-library, which should work well with Jest and others test runners.
Installation
For npm users,
npm install @goji/testing-library --save-dev
For yarn users,
yarn add @goji/testing-library --dev
Read the Jest documentation to setup a Jest config file. You can copy these
three files, jest.config.js
, jestFileMock.js
and jestBabelTransform.js
from
here to your project.
Example
import React, { useState } from 'react';
import { render, fireEvent } from '@goji/testing-library';
import { Input } from '@goji/core';
const ExampleInput = ({ onSave }: { onSave: (value: string) => void }) => {
const [value, setValue] = useState('');
return (
<Input
testID="example-input"
value={value}
onInput={e => setValue(e.detail.value)}
onConfirm={() => {
onSave(value);
setValue('');
}}
/>
);
};
describe('example input', () => {
it('works', () => {
const onSave = jest.fn();
const wrapper = render(<ExampleInput onSave={onSave} />);
const input = wrapper.getByTestId('example-input');
expect(input).toBeTruthy();
// input
fireEvent.input(input, 'hi');
expect(input.props.value).toBe('hi');
// confirm and then cleanup
fireEvent.confirm(input);
expect(onSave).toBeCalledTimes(1);
expect(onSave).toBeCalledWith('hi');
expect(input.props.value).toBe('');
});
});
API
render
render
will run the components in NodeJS by
React Test Renderer. It mount the components and run
their lifecycles as the same way as real Mini Programs.
It returns a wrapper so you can find and enter the specific children element by different queries.
In this case getByTestId
returns the first element which matches the testID
property.
render
Options
Queries
GojiJS provides several types of queries, all of them are combined from variants and By-
queries.
They follow the best practices from the testing library. You can find more information
here.
Variants:
- getBy
- getAllBy
- queryBy
- queryAllBy
- findBy
- findAllBy
By-
queries:
ByTestId(testId: string)
: matches thetestID
propertyByText(text: string | RegExp | ((text: string, node: ReactTestInstance) => boolean))
: matches element's inner text, for more details see TextMatchByProp(propKey: string, propValue: string)
: matches specific property
We recommend to use testID
because it doesn't affect any runtime cost.
Feel free to create an issue if you'd like to append new queries.
debug
This methods print the elements in console for debug purpose.
const wrapper = render(<Comp />);
// print all elements
wrapper.debug();
// print specific elements
wrapper.debug(wrapper.getByTextId('test'));
within
If you'd like to restrict your query in specific container, you can use within
.
const Comp = () => (
<View testID="view">
<Button>Click me</Button>
</View>
);
const wrapper = render(<Comp />);
const view = wrapper.getByTestId('view');
const buttonInsideView = within(view).getByText('Click me');
fireEvent
In your test cases, if you'd like to simulates the interactive events on the elements the
fireEvent.[event]
could help.
For example, to input text into the <Input testID="my-test-id">
element, you can use,
const input = wrapper.getByTestId('my-test-id');
fireEvent.input(input, 'hello, world!');
fireEvent
supports these events,
- tap
- input
- confirm
Feel free to create an issue if you'd like to use new events.
act
The act
function has same behavior in
React Test Renderer. Usually you
should wrap the async component updating in act
.
let increase: Function;
const Comp = () => {
const [count, setCount] = useState(0);
increase = () => setCount(count + 1);
return <View>Count: {count}</View>;
};
const wrapper = render(<Comp />);
act(() => {
// update component
increase();
});
expect(wrapper.getByText('Count: 1')).toBeTruthy;
Please note that all fireEvent
methods are already wrapped by act
so there is no need to wrap
again.
waitFor
and waitForElement
waitFor
catches the assertion error util it passed, otherwise throw after timeout. It's used for
async component tests.
Here is an example,
await waitFor(() => expect(wrapper.getByText('hello')).toBeTruthy());
It equals to,
await waitForElement(() => wrapper.getByText('hello'));