【发布时间】: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,所以我会模拟它。
这就是我的问题所在。我有两种方法。
- 我可以拿到
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
});
});
});
- 我可以模拟
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
});
});
});
我看到第二种方法的价值。但是,我不确定每次我必须与子组件交互时创建简化版本是否值得。
第二种方法的好处是
- 它将代码与测试分离。当用户想要执行搜索时,我的测试不必知道使用什么参数调用哪个方法。 Mock 知道要调用哪个方法,但那是在一个地方,而不是分布在所有测试中。
- 我发现以这种方式编写的测试更具可读性和行为导向。
- 这个模拟可以被提取出来并在多个地方使用。让编写测试更容易。
- 任何方法序列都可以在模拟组件中抽象出来。就像我修改
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