使用 React Hooks 测试库测试轮询

Testing Polling with React Hooks Testing Library

我已经创建了一个名为 usePolling 的自定义挂钩,它接受一个函数来轮询一段时间(由间隔声明)挂钩工作正常但现在我正在尝试测试它......我收到警告:测试中 TestHook 的更新未包含在 act(...) 中。错误,我看了 Kent 关于测试异步挂钩的视频,但是我正在努力让警告消失......但是测试通过了。

jest 抱怨的台词是

} finally {
    > 67 |       setBusy(false);
         |       ^
      68 |     }

这是有道理的,因为这是一个状态变化......但是你会看到我的测试已经包含了所有内容

这是我制作的 GIST > https://gist.github.com/FrancisLeigh/f62bb3c68d16e434019d4843c86e6cf6

提前致谢:slight_smile:

我设法通过接受一个 axiosOverride 道具来使钩子在测试时更加可靠,然后我显然可以更轻松地控制和断言。

任何感兴趣的人 > https://gist.github.com/FrancisLeigh/f62bb3c68d16e434019d4843c86e6cf6

抱歉回复晚了,但我只是设法检查了你的答案,我发现上面的解决方案有点老套,因为它强制创建一个 prop 来覆盖 axios,所以我在下面详细说明了你的问题的可能解决方案.我没有根据您在下面发布的 git link 进行此操作,因为看起来可能会造成混乱。

我们使用 await waitFor 来确保我们可以在调用 promise 之前和之后侦听对 busy 所做的更新非常重要,这样它就不会触发 Warning: An update to TestHook inside a test was not wrapped in act(...) 警告。

我认为这个消息有点欺骗用户,因为用户通常认为将所述问题包装在一个行为中会解决问题,但在这种情况下,问题实际上在于使用 setInterval 并且它能够更新状态。

希望我的解决方案对您有所帮助,并努力弃用 axiosOverrideProp

usePollingHook.js

import * as React from 'react';
import requestAdaptor from './requestAdaptor';

export function usePollingHook(pollTimeMs = 2000) {
    const [busy, setBusy] = React.useState(false);
    const [isPolling, setPolling] = React.useState(false);
    const [data, setData] = React.useState(null);

    React.useEffect(() => {
        if (isPolling) {
            const poll = async () => {
                if (!busy) {
                    setBusy(true);
                    const resp = await requestAdaptor();
                    setBusy(false);
                    setData(resp);
                }
            };
            const interval = setInterval(poll, pollTimeMs);
            return () => clearInterval(interval);
        }

        return () => null;
    }, [isPolling, busy, setBusy, pollTimeMs]);

    return {
        data,
        busy,
        isPolling,
        setPolling,
    };
}

index.spec.js

import { act, renderHook } from '@testing-library/react-hooks';
import { usePollingHook } from './usePollingHook';
import requestAdaptor from './requestAdaptor';

jest.useFakeTimers();
jest.mock('./requestAdaptor', () => jest.fn().mockResolvedValue(1));

describe('usePollingHook', () => {
    it('returns default props', () => {
        const { result } = renderHook(() => usePollingHook());

        expect(result.current.data).toEqual(null);
        expect(result.current.busy).toEqual(false);
        expect(result.current.isPolling).toEqual(false);
        expect(result.current.setPolling).toEqual(expect.any(Function));
    });

    it('ensures the state is updated when the hook has polled a request', async (done) => {
        const { result, waitFor } = renderHook(() => usePollingHook());

        expect(result.current.busy).toEqual(false);

        act(() => {
            result.current.setPolling(true);
        });

        await waitFor(() => expect(result.current.isPolling).toEqual(true));

        act(() => {
            jest.advanceTimersByTime(10000);
        });

        await waitFor(() => expect(result.current.busy).toEqual(true));
        expect(requestAdaptor).toBeCalled();
        await waitFor(() => expect(result.current.busy).toEqual(false));
        expect(result.current.data).toEqual(1);
        done();
    });
});

requestAdaptor.js

一些伪造的 http 函数会环绕一个承诺,例如模拟 axios 或 fetch

export function requestAdaptor() {
    return new Promise((resolve) => {
        return setTimeout(() => {
            return resolve({ userId: 1 });
        }, 1000);
    });
}

export default requestAdaptor;