【问题标题】:How to test side-effect when state hook is passed to a child component using React and jest?使用 React 和 jest 将状态钩子传递给子组件时如何测试副作用?
【发布时间】:2021-04-19 03:33:05
【问题描述】:

我想测试一个子组件,但它依赖于父组件的状态(挂钩)。

孩子接收isModalOpensetIsModalOpen 作为属性。每次isModalOpen 更改时,Child 内部的useEffect 都会触发。但是,当我尝试单独测试Child 组件时,我无法强制更改触发useEffect 挂钩的状态更改。如何在测试环境中不必渲染Parent 来实现这一点?

在下面的示例中,我可以通过渲染Parent 使测试通过。我只想渲染Child,仍然让测试通过。

function Child({ isModalOpen, setIsModalOpen }) {
  useEffect(() => {
    console.log("is the modal open?", isModalOpen);
  }, [isModalOpen]);

  return (
    <button data-testId="myBtn" onClick={() => setIsModalOpen(!isModalOpen)}>
      Click to {isModalOpen ? "close" : "open"} modal
    </button>
  );
}

function Parent() {
    const [isModalOpen, setIsModalOpen] = useState(false);
    return <Child isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen} />;
}

测试:

it("React test", () => {
  let isModalOpen = false;
  const mockFunction = jest.fn((val) => (isModalOpen = val));

  const { queryByTestId } = render(
    <Child isModalOpen={isModalOpen} setIsModalOpen={mockFunction} />
  );

  const btnEl = queryByTestId("myBtn");

  btnEl.click();
  expect(btnEl.innerHTML).toEqual("Click to close modal");

  // -----
  // -----
  // -----
  // ----- The below code works
  // const { queryByTestId } = render(<Parent />);

  // const btnEl = queryByTestId("myBtn");

  // btnEl.click();
  // expect(btnEl.innerHTML).toEqual("Click to close modal");
});

以下是上述代码 sn-p 的工作示例: https://codesandbox.io/s/jest-test-forked-cjm3r?file=/index.test.js

【问题讨论】:

    标签: reactjs jestjs


    【解决方案1】:

    实际上总结起来,如果钩子被传递给孩子,你试图测试的内容与钩子无关。把孩子想象成你的主题在测试中;换句话说,它是您要测试的唯一内容。根据定义,“钩子”是无关紧要的。你传入一个函数,你可以断言一个函数已经被调用了正确的值。

    总结一下,有两种情况

    1. 当模态是 open 并且您单击按钮“单击以关闭模态”按钮时,模态将关闭或以其他方式已知正在使用 false 调用您的模拟函数.
    2. 当模态框关闭并且您单击“单击以关闭模态框”按钮时,模态框将打开或以其他方式已知正在使用 true 调用您的模拟函数.

    为了证明这一点,我对您上面的测试做了一些小改动。随意复制和粘贴它并自己检查,如果您有任何问题,请告诉我。

    注意:从沙盒链接复制并粘贴,稍作修改,以免与原始代码有太大差异。

    it("closes the modal when clicking on the button", () => {
        function Child({ isModalOpen, setIsModalOpen }) {
            useEffect(() => {
                console.log("is the modal open?", isModalOpen);
            }, [isModalOpen]);
    
            return (
                <button data-testId="myBtn" onClick={() => setIsModalOpen(!isModalOpen)}>
                    Click to {isModalOpen ? "close" : "open"} modal
                </button>
            );
        }
    
        // notice the mock here doesn't take arguments
        const mockFunction = jest.fn();
    
        const { queryByTestId } = render(
            // notice that I've set the initial value for isModal to true
            <Child isModalOpen={true} setIsModalOpen={mockFunction} />
        );
    
        const btnEl = queryByTestId("myBtn");
    
        btnEl.click();
        expect(btnEl.innerHTML).toEqual("Click to close modal");
    
        // the assertion here
        expect(mockFunction).toHaveBeenCalledWith(false);
    });
    
    it("opens the modal when clicking on the bottom", () => {
        function Child({ isModalOpen, setIsModalOpen }) {
            useEffect(() => {
                console.log("is the modal open?", isModalOpen);
            }, [isModalOpen]);
    
            return (
                <button data-testId="myBtn" onClick={() => setIsModalOpen(!isModalOpen)}>
                    Click to {isModalOpen ? "close" : "open"} modal
                </button>
            );
        }
    
        const mockFunction = jest.fn();
    
        const { queryByTestId } = render(
            // notice that I've set the initial value for isModal to false
            <Child isModalOpen={false} setIsModalOpen={mockFunction} />
        );
    
        const btnEl = queryByTestId("myBtn");
    
        btnEl.click();
        expect(btnEl.innerHTML).toEqual("Click to open modal");
    
        // the assertion here
        expect(mockFunction).toHaveBeenCalledWith(true);
    });
    

    【讨论】:

    • 很好的解释。我只是建议从测试中删除 Child 组件声明以避免混淆。您应该在测试文件中导入您的组件,而不是在那里创建它:)
    • 感谢您的回答。我想我应该为这个问题添加更多细节,但我尽量让它尽可能简单。我想验证一些副作用,这些副作用仅在状态更改后(在 useEffect 内部)才会触发。阅读此答案后,我假设我无法直接验证这些副作用?对于我的简单示例,副作用是验证按钮内的文本是否已更改。
    • 嗨@John,感觉您的测试方法需要调整。如果您只是测试孩子,您应该模拟不同的状态并断言您的组件正在以正确的方式呈现/响应给定状态(如本答案所示)。尝试测试状态更改本身是在测试 React 代码,这不是必需的 :) 如果您确实有一个非常具体的原因来测试它,那么您应该通过渲染父级来测试,这将真正测试您的代码。希望这是有道理的。
    • 这篇文章可能有助于了解更多信息:kentcdodds.com/blog/…
    • @BrunoMonteiro 感谢您的评论和文章。我想这是有道理的。也许我需要以不同的方式来测试我的组件。我不想测试 React 状态更改本身,只是副作用,但我应该在新状态下测试组件是有道理的。
    猜你喜欢
    • 1970-01-01
    • 2020-05-08
    • 2021-06-30
    • 2019-01-20
    • 2022-01-18
    • 2020-07-21
    • 2020-07-04
    • 2021-07-28
    • 1970-01-01
    相关资源
    最近更新 更多