跳至主要内容

测试

在 React 组件中测试回弹状态

在测试组件时,测试回弹状态可能会有所帮助。您可以使用此模式将新状态与预期值进行比较。它使用 React 函数组件、useRecoilValueuseEffect 来观察 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);