【问题标题】:How to test a function passed as props to child in React?如何在 React 中测试作为道具传递给子级的函数?
【发布时间】:2021-01-29 14:11:41
【问题描述】:

我需要一些关于如何为 React 组件编写测试的建议。我有两种方法,请随时提出更多建议。

我有一个组件App,它呈现一个ComplexSearchBox。我通过这个ComplexSearchBox 一个prop onSearch,这个prop 的值是一个函数。该函数由ComplexSearchBox 在各种用户交互中调用,例如当用户在输入框上按回车键或单击搜索按钮时。

const App = () => {
    return <ComplexSearchBox onSearch={query => console.log(query)}/>;
};

我想为App 组件编写测试。我正在使用 Enzyme 编写测试。 我正在遵循 React 测试库的一些原则。 我正在安装组件。所以这也会渲染所有的孩子。我不想渲染ComplexSearchBox,所以我会模拟它。

这就是我的问题所在。我有两种方法。

  1. 我可以拿到ComplexSearchBox的props,直接用需要的参数调用方法。
jest.mock('Path To ComplexSearchBox', function ComplexSearchBox() {
    return null;
});

describe('App', () => {
    describe('when search button is clicked', () => {
        it('should log to console by invoking prop method', () => {
            const wrapper = mount(<App/>);
            wrapper.find('ComplexSearchBox').props().onSearch('My random test query');
            //expect something
        });
    });
});
  1. 我可以模拟ComplexSearchBox 并返回它的简化版本。现在我可以在输入框中输入查询,然后单击按钮提交。
jest.mock('Path To ComplexSearchBox', function ComplexSearchBox({onSearch}) {
    const [query, setQuery] = useState('Sample Search Query');
    return <div>
        <input value={query} onChange={setQuery}/>
        <button onClick={() => onSearch(query)}/>
    </div>;
});

describe('App', () => {
    describe('when search button is clicked', () => {
        it('should log to console by clicking', () => {
            const wrapper = mount(<App/>);
            wrapper.find('input').simulate('change', {target: {value: 'My random test query'}});
            wrapper.find('button').simulate('click');
            //expect something
        });
    });
});

我看到第二种方法的价值。但是,我不确定每次我必须与子组件交互时创建简化版本是否值得。

第二种方法的好处是

  1. 它将代码与测试分离。当用户想要执行搜索时,我的测试不必知道使用什么参数调用哪个方法。 Mock 知道要调用哪个方法,但那是在一个地方,而不是分布在所有测试中。
  2. 我发现以这种方式编写的测试更具可读性和行为导向。
  3. 这个模拟可以被提取出来并在多个地方使用。让编写测试更容易。
  4. 任何方法序列都可以在模拟组件中抽象出来。就像我修改ComplexSearchBox 如下。
const App = () => {
    return <ComplexSearchBox preProcess={()=>{}} onSearch={query => console.log(query)} postProcess={()=>{}}/>;
};

jest.mock('Path To ComplexSearchBox', function ComplexSearchBox({preProcess, onSearch, postProcess}) {
    const [query, setQuery] = useState('Sample Search Query');
    const search = (query) => {
        const preProcessedQuery = preProcess(query);
        const searchedQuery = onSearch(preProcessedQuery);
        postProcess(searchedQuery);
    };
    return <div>
        <input value={query} onChange={setQuery}/>
        <button onClick={() => search(query)}/>
    </div>;
});

虽然我不太确定最后一个好处是否真的是一个好处。现在我的模拟知道ComplexSearchBox 的生命周期。但另一方面,这个模拟将只编写一次,这样我就不用在很多测试中一个接一个地调用这 3 个方法。 我还可以争辩说,使用方法一编写的组件测试不应该真正关心方法排序,因为这是ComplexSearchBox 的责任。 这 3 种方法确实具有紧密耦合,因为一个输出是下一个输入。现在我正在对这两个组件进行边界集成测试。

我还可以有 3 个带有 onClick 的按钮来运行这 3 个方法,现在我可以单独测试它们。

我不确定哪种方法更好。我有点倾向于方法 2,因为它使我的测试不那么依赖于实现。

如果您有其他方法来测试此场景,我将不胜感激,请分享。

【问题讨论】:

    标签: javascript reactjs unit-testing testing jestjs


    【解决方案1】:

    我没有确切的答案给你,但我强烈建议阅读这篇文章:

    https://kentcdodds.com/blog/why-i-never-use-shallow-rendering

    在其中,你会发现反对浅层渲染的论据,以及支持或在 React 中进行集成测试的论据。

    您提出的观点都是有效的,如果您选择单元测试的路线,因此模拟 &lt;App /&gt; 组件的所有子组件,您将把您的测试耦合到这些子组件的当前实现,并且随时更改这些子组件,即使重构没有改变行为,你也有破坏&lt;App /&gt;s 测试的风险。

    我不相信在处理模拟时有办法绕过它,所以我建议考虑上面的文章而不是模拟任何东西,这可能会使测试运行得慢一点,但可能不会大部分时间都很重要!

    【讨论】:

    • 感谢您的回复。几年前阅读 Kent 的博客后,我采用了 RTL 的一些原则。我停止使用“setState”并使用“instance”调用方法。测试不应该知道实现。我开始使用“mount”而不是“shallow”。但是,我不同意不嘲笑任何事情。测试高级容器组件成为一项挑战,最终以脆弱的测试告终。 Kent 提到了他如何嘲笑第三方库。我使用相同的方式来模拟子组件。当我将函数作为道具传递时,我的问题就出现了。我是调用一个道具还是让模拟的孩子调用道具。
    • 通常,当我模拟组件时,我会断言它们是使用正确的道具(在你的情况下是函数)调用的,并且我模拟了我需要的测试的所有必要行为的结果。所以我不会调用作为 prop 传递的函数,我只会断言组件已正确接收它,并假设行为在子组件内正确发生。
    • 如果不调用传递的函数,那么如何测试传递的函数?我可以更改该函数内部的逻辑,并且不会破坏任何单元测试。
    • 是的,这是一个有效的问题,但我会建议另一种解决方案。您不断模拟子组件的执行/返回,并将要测试的函数提取到帮助文件中,您可以在其中单独对函数进行单元测试。
    • 您的“提取”方法是一个有效的解决方案。但是,它可能并不总是可行的。通常,传递给孩子的函数类似于“setState”或“() => dispatch(someAction())”。它涉及传递与 react/redux 相关的内容,例如“setState”或“dispatch”或其他内容。将其提取到辅助函数可能有点过于复杂。如果我要传递这样一个函数,那么我更喜欢使用我提到的两种方法中的任何一种来调用该函数。
    猜你喜欢
    • 1970-01-01
    • 2020-04-22
    • 2022-01-24
    • 2016-05-13
    • 2021-11-02
    • 2021-05-26
    • 2018-08-01
    • 2017-10-31
    相关资源
    最近更新 更多