卸载组件时不调用测试集状态
我有一个自定义钩子,它在页面加载时调用一个承诺来设置数据。为了确保在组件卸载时不会调用数据/错误的设置状态,我正在使用取消请求,如下所示:
function useService() {
const [data, setData] = useState([]);
const [error, setError] = useState("");
const [loading, setLoading] = useState({});
const [cancelRequest, setCancelRequest] = useState(false);
const getMessgaes = async () => {
setLoading(true);
try {
const res = await getChatLog();
if (!cancelRequest) {
setData(res);
setLoading(false);
}
} catch (e) {
if (!cancelRequest) {
setError(e);
setLoading(false);
}
}
};
useEffect(() => {
getMessgaes();
return () => {
setCancelRequest(true);
};
}, []);
return {
data, error, loading, cancelRequest
}
}
我的测试是:
it("if component is unmounted before response then set data is not called", async () => {
getChatLog.mockImplementation(() => {
setTimeout(()=>{
Promise.reject("error");
},500);
});
const {result, unmount, waitForNextUpdate} = renderHook(() => useService());
expect(result.current.cancelRequest).toEqual(false);
unmount();
expect(result.current.cancelRequest).toEqual(true);
await waitForNextUpdate();
expect(getChatLog).toHaveBeenCalledTimes(3);
expect(result.current.error).toEqual("");
});
但是我收到错误:
Warning: An update to TestHook inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in TestHook
in Suspense
有人可以指导如何测试吗?
谢谢
首先,永远不要在useEffect
钩子的清理函数中调用setState()
,就像 componentWillUnmount(( 一样。
您不应该在
componentWillUnmount()
中调用 setState((,因为组件永远不会被重新渲染。卸载组件实例后,将永远不会再次挂载该实例。
因此,清理useEffect
测试代码没有意义。
其次,使用带有延迟的setTimeout()
进行模拟实现也没有意义,我们不需要在测试用例中延迟它,它使测试用例变慢,我们必须为它模拟计时器。您可以改用mock.mockRejectedValueOnce('error')
。
最后,您应该在断言之前await waitForNextUpdate()
解决警告:Warning: An update to TestComponent inside a test was not wrapped in act(...).
。
例如
useService.ts
:
import { useEffect, useState } from 'react';
import { getChatLog } from './getChatLog';
export function useService() {
const [data, setData] = useState<string[]>([]);
const [error, setError] = useState('');
const [loading, setLoading] = useState({});
const [cancelRequest, setCancelRequest] = useState(false);
const getMessgaes = async () => {
setLoading(true);
try {
const res = await getChatLog();
if (!cancelRequest) {
setData(res);
setLoading(false);
}
} catch (e) {
if (!cancelRequest) {
setError(e);
setLoading(false);
}
}
};
useEffect(() => {
getMessgaes();
}, []);
return { data, error, loading, cancelRequest };
}
getChatLog.ts
:
export async function getChatLog() {
return ['real data'];
}
useService.test.ts
:
import { renderHook } from '@testing-library/react-hooks';
import { useService } from './useService';
import { getChatLog } from './getChatLog';
import { mocked } from 'ts-jest/utils';
jest.mock('./getChatLog');
describe('58026328', () => {
test('should pass', async () => {
const mGetChatLog = mocked(getChatLog);
mGetChatLog.mockRejectedValueOnce('error');
const { result, waitForNextUpdate } = renderHook(useService);
await waitForNextUpdate();
expect(result.current.cancelRequest).toEqual(false);
});
});
测试结果:
PASS examples/58026328/useService.test.ts
58026328
✓ should pass (20 ms)
---------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------|---------|----------|---------|---------|-------------------
All files | 82.61 | 25 | 80 | 81.82 |
getChatLog.ts | 50 | 100 | 0 | 50 | 2
useService.ts | 85.71 | 25 | 100 | 85 | 14-16
---------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.56 s, estimated 13 s