测试
在 React 组件中测试回弹状态
在测试组件时,测试回弹状态可能会有所帮助。您可以使用此模式将新状态与预期值进行比较。它使用 React 函数组件、useRecoilValue
和 useEffect
来观察 atom
/selector
的变化,并在用户执行每次修改状态的操作时执行回调。
export const RecoilObserver = ({node, onChange}) => {
const value = useRecoilValue(node);
useEffect(() => onChange(value), [onChange, value]);
return null;
};
node
:可以是原子或选择器。onChange
:每当状态发生变化时,将调用此函数。
示例:用户修改的表单状态
组件
const nameState = atom({
key: 'nameAtom',
default: '',
});
function Form() {
const [name, setName] = useRecoilState(nameState);
return (
<form>
<input
data-testid="name_input"
type="text"
value={name}
onChange={event => setName(event.target.value)}
/>
</form>
);
}
测试
describe('The form state should', () => {
test('change when the user enters a name.', () => {
const onChange = jest.fn();
render(
<RecoilRoot>
<RecoilObserver node={nameState} onChange={onChange} />
<Form />
</RecoilRoot>,
);
const component = screen.getByTestId('name_input');
fireEvent.change(component, {target: {value: 'Recoil'}});
expect(onChange).toHaveBeenCalledTimes(2);
expect(onChange).toHaveBeenCalledWith(''); // Initial state on render.
expect(onChange).toHaveBeenCalledWith('Recoil'); // New value on change.
});
});
在 React 组件中测试带有异步查询的回弹状态
原子的常见模式是使用异步查询来获取原子的状态、选择器或作为效果的一部分。这会导致组件被挂起。但是,在测试过程中,挂起的组件不会在没有执行的情况下更新 DOM。要测试这种情况,我们需要一个辅助函数
// act and advance jest timers
function flushPromisesAndTimers(): Promise<void> {
return act(
() =>
new Promise(resolve => {
setTimeout(resolve, 100);
jest.runAllTimers();
}),
);
}
示例:带有从异步数据查询返回的数据的标题
组件
const getDefaultTitleAtomState = async () => {
const response = await fetch('https://example.com/returns/a/json');
return await response.json(); // { title: 'real title' };
};
const titleState = atom({
key: 'titleState',
default: getDefaultTitleAtomState(),
});
function Title() {
const data = useRecoilValue(titleState);
return (
<div>
<h1>{data.title}</h1>
</div>
);
}
测试
describe('Title Component', () => {
test('display the title correctly', async () => {
const mockState = {title: 'test title'};
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve(mockState),
}),
);
render(
<RecoilRoot>
<Suspense fallback={<div>loading...</div>}>
<Title />
</Suspense>
</RecoilRoot>,
);
await flushPromisesAndTimers();
expect(screen.getByText(mockState.title)).toBeInTheDocument();
expect(screen.getByText('loading...')).not.toBeInTheDocument();
});
});
在自定义钩子中测试回弹状态
有时编写依赖于回弹状态的自定义 React 钩子很方便。这些需要包装在 <RecoilRoot>
中。React Hooks Testing Library 可以帮助实现此模式。
示例:React Hooks Testing Library
状态
const countState = atom({
key: 'countAtom',
default: 0,
});
钩子
const useMyCustomHook = () => {
const [count, setCount] = useRecoilState(countState);
// Insert other Recoil state here...
// Insert other hook logic here...
return count;
};
测试
test('Test useMyCustomHook', () => {
const {result} = renderHook(() => useMyCustomHook(), {
wrapper: RecoilRoot,
});
expect(result.current).toEqual(0);
});
在 React 外部测试回弹状态
在 React 上下文之外操作和评估回弹选择器以进行测试可能很有用。这可以通过使用回弹 Snapshot
来完成。您可以使用 snapshot_UNSTABLE()
构建一个新的快照,然后使用该 Snapshot
来评估选择器以进行测试。
示例:Jest 单元测试选择器
const numberState = atom({key: 'Number', default: 0});
const multipliedState = selector({
key: 'MultipliedNumber',
get: ({get}) => get(numberState) * 100,
});
test('Test multipliedState', () => {
const initialSnapshot = snapshot_UNSTABLE();
expect(initialSnapshot.getLoadable(multipliedState).valueOrThrow()).toBe(0);
const testSnapshot = snapshot_UNSTABLE(({set}) => set(numberState, 1));
expect(testSnapshot.getLoadable(multipliedState).valueOrThrow()).toBe(100);
});
测试异步选择器
在测试异步选择器时,需要retain()
快照以避免过早取消。
const initialSnapshot = snapshot_UNSTABLE();
const release = initialSnapshot.retain();
try {
// your test
} finally {
release();
}
清除所有选择器缓存
选择器缓存是在 <RecoilRoot>
和测试之间共享的,因此您可能需要在每次测试后清除缓存。
const clearSelectorCachesState = selector({
key: 'ClearSelectorCaches',
get: ({getCallback}) => getCallback(({snapshot, refresh}) => () => {
for (const node of snapshot.getNodes_UNSTABLE()) {
refresh(node);
}
}),
});
const clearSelectorCaches = testingSnapshot.getLoadable(clearSelectorCachesState).getValue();
// Assuming we're in a file added to Jest's setupFilesAfterEnv:
afterEach(clearSelectorCaches);