跳到主要内容

renderDataHook()

renderDataHook() 用于测试依赖 Reactive Data Client 的 hook。它与 @testing-library/react-hooks 的 renderHook() 相仿,但会额外提供一个 <Suspense/> 边界,并运行在 <Provider /> 上下文中。

备注

renderDataHook() 会使用新的 manager 实例创建一个 Provider 上下文。这意味着每次调用 renderDataHook() 都会得到全新的缓存状态和 manager 状态。

类型
type RenderDataHook = {
<P, R, T = any>(
callback: (props: P) => R,
options?: {
initialProps?: P;
initialFixtures?: Fixture[];
resolverFixtures?: (Fixture | Interceptor<T>)[];
getInitialInterceptorData?: () => T;
wrapper?: React.ComponentType;
},
): {
rerender: (props?: Props) => void;
result: {
current: Result;
error?: Error;
};
unmount: () => void;
controller: Controller;
cleanup(): void;
allSettled(): Promise<unknown>;
/* @deprecated */
waitForNextUpdate: (options?: waitForOptions) => Promise<void>;
waitFor<T>(
callback: () => Promise<T> | T,
options?: waitForOptions,
): Promise<T>;
};
/** cleanup is automatic; only needed for ordering (e.g., before jest.useRealTimers()) */
cleanup(): void;
allSettled(): Promise<unknown>;
};

用法​

import { useSuspense } from '@data-client/react';
import { renderDataHook } from '@data-client/test';
import { Article, ArticleResource } from './resources/Article';

const response = {
id: 5,
title: 'hi ho',
content: 'whatever',
tags: ['a', 'best', 'react'],
};

it('useSuspense() should render the response', async () => {
const { result, waitFor } = renderDataHook(
() => {
return useSuspense(ArticleResource.get, { id: 5 });
},
{
initialFixtures: [
{
endpoint: ArticleResource.get,
args: [{ id: 5 }],
response,
},
],
},
);
expect(result.current instanceof Article).toBe(true);
expect(result.current.title).toBe(response.title);
});

参数​

callback​

在 React 中运行的 hook。其返回值可以通过 result.current 获取

options.initialFixtures​

如果测试需要缓存中预先存在数据,可以用它来预填充缓存。接收一个 fixture 数组

其效果等同于使用 mockInitialState() 初始化 <DataProvider />

options.resolverFixtures​

这些 fixture 或 interceptor 用于响应任何新的请求。它最适合用来模拟变更这类命令式请求,也可以用来测试挂起状态或过渡。

其实现方式是添加 MockResolver 作为 wrapper。

options.getInitialInterceptorData​

用于为所有 interceptor 初始化 this 属性的函数。

options.initialProps​

传给回调函数的初始值

options.wrapper​

将一个 React 组件作为 wrapper 选项传入,它会被渲染在内部元素的外层

返回值​

controller​

用于 dispatch 命令式副作用的 Controller

import { act } from '@testing-library/react';
import { useSuspense } from '@data-client/react';
import { renderDataHook } from '@data-client/test';
import { Todo, TodoResource } from './resources/Todo';

it('should update', async () => {
const id = 5;
const payload = { title: 'first item', id, completed: false };
const { result, controller } = renderDataHook(
() => {
return useSuspense(TodoResource.getList);
},
{
initialFixtures: [
{
endpoint: TodoResource.getList,
args: [],
response: [payload],
},
],
resolverFixtures: [
{
endpoint: TodoResource.update,
response: ({ id }, body) => ({ ...body, id }),
},
],
},
);
expect(result.current).toEqual([Todo.fromJS(payload)]);
await act(async () => {
await controller.fetch(
TodoResource.update,
{ id },
{ title: 'updated title' },
);
});
expect(result.current[0].title).toBe('updated title');
});

cleanup()​

清理本次渲染中使用的所有 manager。这在模拟定时器时尤其重要,因为 Reactive Data Client 的内部实现依赖真实的定时器来避免竞态条件。

清理会通过模块级的 afterEach hook 在每个测试结束后自动运行(与 @testing-library/react 类似)。只有当你必须在测试体内控制清理顺序时才需要手动调用——例如,在从模拟定时器切换回真实定时器之前进行清理:

it('should handle polling', async () => {
jest.useFakeTimers();
const { result } = renderDataHook(/* ... */);
// ... assertions ...
renderDataHook.cleanup(); // must run while fake timers are still active
jest.useRealTimers();
});

allSettled()​

返回一个 promise,在所有进行中的请求完成后 resolve。

每次调用 renderDataHook() 的返回值上也提供了该方法。

result​

  • current(any)——callback 函数的返回值
  • error(Error)——如果 callback 函数在渲染期间抛出错误,则为所抛出的错误

waitFor​

返回一个 Promise,当提供的回调执行时没有抛出异常、并返回真值或 undefined 时 resolve。可以放心地在回调中使用 renderDataHook 的结果来进行断言或检验值。

waitForNextUpdate​

已弃用

请改用 waitFor

返回一个 Promise,在 hook 下一次渲染时 resolve,通常发生在异步操作导致状态更新时。

rerender​

(function([newProps]))——用于重新渲染测试组件(包括 callback 函数中调用的所有 hook)的函数。如果传入了 newProps,在之后的渲染中它们会替换传给 callback 函数的 initialProps。

unmount​

(function())——用于卸载测试组件的函数,通常用来触发 useEffect hook 的清理副作用。

示例​

import { useSuspense } from '@data-client/react';
import { renderDataHook } from '@data-client/test';
import { Article, ArticleResource } from './resources/Article';

const response = {
id: 5,
title: 'hi ho',
content: 'whatever',
tags: ['a', 'best', 'react'],
};

it('should resolve useSuspense()', async () => {
const { result, waitFor, controller } = renderDataHook(
() => {
return useSuspense(ArticleResource.get, response);
},
{
resolverFixtures: [
{
endpoint: ArticleResource.get,
response: ({ id }) => ({ ...response, id }),
},
{
endpoint: ArticleResource.partialUpdate,
response: ({ id }, body) => ({ ...body, id }),
},
],
},
);
// this indicates suspense
expect(result.current).toBeUndefined();
await waitFor(() => expect(result.current).toBeDefined());
expect(result.current instanceof Article).toBe(true);
expect(result.current.title).toBe(response.title);
await controller.fetch(
ArticleResource.partialUpdate,
{ id: response.id },
{ title: 'updated title' },
);
expect(result.current.title).toBe('updated title');
});