【问题标题】:How to test a functional component that takes a callback function as a prop that updates state via useState hook?如何测试将回调函数作为通过 useState 钩子更新状态的道具的功能组件?
【发布时间】:2020-05-21 18:41:11
【问题描述】:

目前正在尝试测试烤面包机组件中的删除功能。使用了一个回调来更新父组件的状态。由于当前无法访问 setter 函数,因此在模拟单击时测试失败。

import React from "react";
import {mount} from "enzyme";
import {CcngToaster, ToasterPosition, ToastStatus} from "./CcngToaster";

 it("Deletes toaster on button click", () => {
        const mockState = [
            {
                id: 0,
                title: "Tea",
                message: "Towel",
                status: ToastStatus.ERROR,
                position: ToasterPosition.TOPRIGHT,
                autoDelete: false,
                autoDeleteTime: 0
            }
        ];
        const wrapper = mount(<CcngToaster toastList={mockState} setToastList={jest.fn()}/>);
        expect(wrapper.find(".toast").length).toBe(1);
        const closeButton = wrapper.find("button");
        closeButton.simulate("click");//Doesn't work since setToastList has no function that will update state
        expect(wrapper.find(".toast").length).toBe(0);//test fails here
        wrapper.unmount();
    });

我通过 useState 设置状态,然后将 toast 的 setter 函数传递给 toaster 组件

const App: React.FC = () => {
    const [toasts, setToasts] = useState<ToastInfo[]>([{
        id: 0,
        title: "Title",
        message: "Message",
        status: ToastStatus.ERROR,
        position: ToasterPosition.BOTTOMRIGHT,
        autoDelete: true,
        autoDeleteTime: 5000
    }]);

    return (
        <div style={{height: "100vh", width: "100vw"}}>
            <CcngToaster toastList={toasts} setToastList={setToasts}/>
        </div>
    );
};

toaster 组件获取外部状态和函数来更新它

interface InternalToastState {
    [id: number]: {timeoutHandle: ReturnType<typeof setTimeout>}
}

interface Props {
    toastList: ToastInfo[];
    setToastList: (newValue: ToastInfo[]) => void;
}

export const CcngToaster: React.FC<Props> = ({toastList, setToastList}: Props) => {
    const [internalState, setInternalState] = useState<InternalToastState>({});

useEffect(() => {
        setInternalState(internalState);
    }, [internalState]);

    const deleteToast = (id: number) => {
        const toastListItem = toastList.findIndex(e => e.id === id);
        toastList.splice(toastListItem, 1);
        setToastList(toastList.slice());

        const internalStateValue = internalState[id];
        if (!internalStateValue) return;
        clearTimeout(internalStateValue.timeoutHandle);
        delete internalState[id];
    };
...

【问题讨论】:

    标签: reactjs typescript jestjs enzyme


    【解决方案1】:

    您的测试显然无法正常工作,因为设置 setToastList={jest.fn()} 会导致您的点击不执行任何操作。您需要在那里传递一些可以重新渲染您的组件的功能。您有两个选择:

    1. 使用您在那里设置的状态测试您的整个 App 组件。
    2. 创建具有模拟状态的测试包装器,您可以在测试中使用 mount,如下所示:
    const TestWrapper = () => {
      const [mockState, setMockState] = useState(/*some initial data*/);
    
      return (<CcngToaster toastList={mockState} setToastList={setMockState}/>
    }
    

    然后在测试中使用这个组件:

    it("Deletes toaster on button click", () => {
      const wrapper = mount(<TestWrapper />);
      expect(wrapper.find(".toast").length).toBe(1);
      const closeButton = wrapper.find("button");
      closeButton.simulate("click");
      expect(wrapper.find(".toast").length).toBe(0);//test fails here
      wrapper.unmount();
    });
    

    应该可以。您可以选择在 TestWrapper 中创建一个 prop 来传递来自测试的初始模拟状态,而不是在内部进行硬编码。

    【讨论】:

    • 这修复了它。非常感谢您的帮助!
    猜你喜欢
    • 2021-08-24
    • 2019-08-15
    • 1970-01-01
    • 2020-02-25
    • 2021-05-16
    • 2023-03-18
    • 1970-01-01
    • 1970-01-01
    • 2020-01-03
    相关资源
    最近更新 更多