【译】如何浅渲染(shallow render) Jest 快照
如果你的组件正在使用 Jest 快照测试,下面是一些你必须注意的陷阱。这两条你可能会遇到在你编写测试的时候:
- 如果实际的快照测试渲染了一个包含很多子组件的组件,那么输出的快照测试将变得太大。这个本身就存在两个问题:A) 仅仅通过查看快照并不能准确的发现快照不同的地方;B) 如果你同时快照测试你的子组件,最终会得到重复的快照输出。
- 如果你实际的快照测试组件渲染了大量子组件,则需要在父组件的快照测试中设置子组件的所有 props。因此,你并没有真正将关注点放在父组件上,而是设置子组件所有必要的信息。如果你再分开测试你的子组件,工作将变得重复,因为你必须使用相同的 props 设置来测试他们。最终你会得到重复的测试设置。
如你所见,这两个问题只适用于渲染多个子组件的父组件。因此,如果你可以在快照测试中浅层渲染(shadow render
)父组件,从而在测试中只关注父组件;以及不管有没有渲染了子组件都不用担心子组件的输出。
如果你正在使用 Jest 快照测试,你可能使用 react-test-renderer 渲染你的 React 组件:
import React from 'react';
import renderer form 'react-test-renderer';
import Profile from '.';
describe('Profile', () => {
it('renders', () => {
const component = renderer.create(<Profile />);
const tree = component.toJSON();
expect(tree).toMatchSnapShot();
});
});
如果在 Profile 组件中呈现许多子组件,那么快照测试输出可能会出现问题1。不过,您唯一应该关心的是呈现的组件实例,而不是它们的内容:
const Profile = () => (
<>
<Preferences />
<Documents />
<WorkExperience />
<Education />
<Skills />
<PersonalInfo />
</>
);
如果 Profile 组件传递大量的 props 给所有的子组件,那么就会出现第二个问题,因为你必须为所有的子组件设置 props 在你快照测试,即使父组件可能并不关心它们:
const Profile = ({
...preferencesProps,
...documentsProps,
...workExperienceProps,
...educationProps,
...skillsProps,
...personalInfoProps,
}) => (
<>
<Preferences {...preferencesProps} />
<Documents {...documentsProps} />
<WorkExperience {...workExperienceProps} />
<Education {...educationProps} />
<Skills {...skillsProps} />
<PersonalInfo {...personalInfoProps} />
</>
);
你希望避免上面两个问题在父组件快照测试的时候,因为这些问题应该在子组件他们自己进行测试。父组件只关心渲染这些子组件。
笔记:浅层渲染(Shallow rendering)快照测试对于你整体的测试战略来说并不是灵丹妙药,你可能会对你的组件工作在集成环境失去信心(例如:父子组件交互)
尽管 React 的测试渲染器提供了 shallow rendering ,但我发现虚拟子组件的渲染输出更适合我的测试用例:
import React from 'react';
import renderer from 'react-test-renderer';
import Profile from '.';
jest.mock('./Preferences', () => () => 'Preferences');
jest.mock('./Documents', () => () => 'Documents');
jest.mock('./WorkExperience', () => () => 'WorkExperience');
jest.mock('./Education', () => () => 'Education');
jest.mock('./Skills', () => () => 'Skills');
jest.mock('./PersonalInfo', () => () => 'PersonalInfo');
describe('Profile', () => {
it('renders', () => {
const component = renderer.create(<Profile />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
你的浅层渲染快照测试输出可能看起来像下面这样:
exports[`Profile renders 1`] = `
Array [
"Preferences",
"Documents",
"WorkExperience",
"Education",
"Skills",
"PersonalInfo",
]
`;
与渲染所有子组件来做版本比较相比,它做到了最大程度的简化。此外,你也不需要关心 props 的传递。然而,如果你想测试你的父组件是否传递了必须的 props 给它的子组件,你甚至可以用一个虚拟的子组件来测试它:
import React from 'react';
import renderer from 'react-test-renderer';
import Profile from '.';
import PersonalInfo from './PersonalInfo';
jest.mock('./Preferences', () => () => 'Preferences');
jest.mock('./Documents', () => () => 'Documents');
jest.mock('./WorkExperience', () => () => 'WorkExperience');
jest.mock('./Education', () => () => 'Education');
jest.mock('./Skills', () => () => 'Skills');
jest.mock('./PersonalInfo', () => () => 'PersonalInfo');
describe('Profile', () => {
it('renders and passes props', () => {
const component = renderer.create(<Profile />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
expect(component.root.findByType(PersonalInfo).props).toEqual({
name: 'Robin Wieruch',
});
});
});
总而言之,你最终为父组件创建了一个非常轻量级的快照测试,而你将在子组件自身更彻底地进行快照测试。
React 官方文档
测试渲染器 Test Renderer(https://reactjs.org/docs/test-renderer.html
导入
import TestRenderer from 'react-test-renderer'; // ES6
const TestRenderer = require('react-test-renderer'); // ES5 with npm
总览
这个包提供了一个 React 渲染器(React renderer
)可以将 React 组件渲染为纯的 JavaScript 对象,而不用依赖于 DOM 或本地移动环境。
本质上,这个包可以使你轻松抓取一个由 React DOM 或 React Native 组件渲染的平台视图层级(类似于 DOM 树)快照,而无需使用浏览器或 jsdom。
例子:
import TestRenderer from 'react-test-renderer';
function Link(props) {
return <a href={props.page}>{props.children}</a>;
}
const testRenderer = TestRenderer.create(
<Link page="https://www.fackbook.com/">
);
console.log(testRenderer.toJSON());
// { type: 'a',
// props: { href: 'https://www.facebook.com/' },
// children: [ 'Facebook' ] }
你可以使用 Jest 的快照测试特性自动化地保存一份 JSON 树的复制到一个文件中,并检查你的测试没有发生变化:这里学习到更多。
你也可以遍历输出来找到特殊的节点,并对它们进行断言。
import TestRenderer from 'react-test-renderer';
function MyComponent() {
return (
<div>
<SubComponent foo="bar" />
<p className="my">Hello</p>
</div>
)
}
function SubComponent() {
return (
<p className="sub">Sub</p>
);
}
const testRenderer = TestRenderer.create(<MyComponent />);
const testInstance = testRenderer.root;
expect(testInstance.findByType(SubComponent).props.foo).toBe('bar');
expect(testInstance.findByProps({className: "sub"}).children).toEqual(['Sub']);
参考
TestRenderer.create()
TestRenderer.create(element, options);
使用传递的 React 元素创建一个 TestRenderer 实例。它不使用真正的DOM,但仍然将组件树完全呈现到内存中,因此您可以对其进行断言。返回一个 TestRenderer 实例。
TestRenderer.act()
TestRenderer.act(callback);
类似于 react-dom/test-utils 里面的 act() 方法,TestRenderer.act 为断言准备一个组件。使用这个版本的 act() 就是封装调用 TestRenderer.create and TestRenderer.update。
import {create, act} from 'react-test-renderer';
import App from './app.js'; // The component being tested
// render the component
let root;
act(() => {
root = create(<App value={1}/>)
});
// make assertions on root
expect(root.toJSON()).toMatchSnapshot();
// update with some different props
act(() => {
root = root.update(<App value={2}/>);
})
// make assertions on root
expect(root.toJSON()).toMatchSnapshot();
testRenderer.toJSON()
testRenderer.toJSON();
返回一个对象呈现渲染树🌲。这个树仅仅包含平台特定的节点,如 <div> 或 <View> 和他们的 props,但是不包含用户编写的组件。这对于快照测试十分方便。
testRenderer.toTree()
和 toJson() 方法唯一的区别就是它包含用户编写的组件。
......
React 文档
Shallow Renderer
导入
import ShallowRenderer from 'react-test-renderer/shallow'; // ES6
var ShallowRenderer = require('react-test-renderer/shallow'); // ES5 with npm
总览
在编写React的单元测试时,浅层呈现可能会有帮助。浅层呈现允许您呈现一个组件“一层深”,并断言关于其呈现方法返回什么的事实,而不用担心子组件的行为,这些子组件没有实例化或呈现。这并不需要DOM。
例如,如果你拥有如下组件:
function MyComponent() {
return (
<div>
<span className="heading">Title</span>
<Subcomponent foo="bar" />
</div>
);
}
然后你可以断言:
import ShallowRenderer from 'react-test-renderer/shallow';
// in your test:
const renderer = new ShallowRenderer();
renderer.render(<MyComponent />);
const result = renderer.getRenderOutput();
expect(result.type).toBe('div');
expect(result.props.children).toEqual([
<span className="heading">Title</span>,
<Subcomponent foo="bar" />
shallow 测试当前有一些限制,例如不支持 refs。
提示:我们建议你使用 Enzyme 的 Shallow Rendering API 它在相同的功能上提供了更高级的 API
参考
shallowRenderer.render()
您可以将shallowRenderer视为呈现正在测试的组件的“位置”,并从中提取组件的输出。
render()类似于ReactDOM.render(),但它不需要DOM,只呈现一个层次的深度。这意味着您可以测试与子组件的实现方式隔离的组件。
shallowRenderer.getRenderOutput()
调用了shallowRenderer.render()之后,您可以使用shallowRenderer.getRenderOutput()获得浅呈现的输出。
然后可以开始断言关于输出的事实。