Jest 快照测试
Jest snapshots testing
我对我的组件之一使用 jest 快照测试,生成的快照文件很大(199Kb 和 4310 行)。当快照测试失败时,所有快照文件都会打印到控制台(即渲染 3-4 秒),这给了我这种 "you're doing something wrong" 的感觉。
所以我的问题是:我是否正确使用快照测试?
组件代码:
import _ = require('lodash');
import React = require('react');
import {TranslatedMessage} from 'translator';
import {UserProfile} from './user-profile';
import {ICustomerProfile} from '../customer/customer-profile';
interface IUserProfile {
firstName: string;
lastName: string;
id: string;
customer: ICustomerProfile;
job: string;
email: string;
contacts: string;
phoneNumber: string;
}
interface IUserProfileProps {
contact: IUserProfile;
}
interface IUserProfileState {}
export class UserProfile extends React.Component<IUserProfileProps, IUserProfileState> {
constructor(props: IUserProfileProps) {
super(props);
}
public render(): JSX.Element {
return (
<div className="ext-admin-user-infos-details">
<div className="ext-admin-user-infos-details-content">
<div className="row">
<div className="col-md-12">
<h3>{this.props.contact.firstName } {this.props.contact.lastName}</h3>
<p className="ext-subtitle">
<span className="ext-minor">{this.props.contact.id}</span>
</p>
</div>
</div>
<div className="row">
<div className="col-md-8">
<div className="ext-admin-user-infos-card">
<h6>
<TranslatedMessage messageKey="common.labels.customer" />
</h6>
<ul>
<li>{this.props.contact.customer.name}</li>
</ul>
</div>
<div className="ext-admin-user-infos-card">
<h6>
<TranslatedMessage messageKey="admin.contact.infos.job" />
</h6>
<ul>
<li>{this.props.contact.job}</li>
</ul>
</div>
<div className="ext-admin-user-infos-card">
<h6>
<TranslatedMessage messageKey="admin.contact.infos.email" />
</h6>
<ul>
<li>{this.props.contact.email}</li>
</ul>
</div>
</div>
<div className="col-md-4">
<div className="ext-admin-user-infos-card">
<h6>
<TranslatedMessage messageKey="common.labels.followed" />
</h6>
<ol>
{this.renderContacts(this.props.contact.contacts)}
</ol>
</div>
<div className="ext-admin-user-infos-card">
<h6>
<TranslatedMessage messageKey="common.labels.phone" />
</h6>
<ul>
<li>{this.props.contact.phoneNumber}</li>
</ul>
</div>
</div>
</div>
</div>
</div>
);
}
protected renderContacts(contacts: IUserProfile[]): JSX.Element[] {
let contacts= [];
if (sales) {
_.map(sales, function(contact: IUserProfile): void {
salesContact.push(
<li>
{ contact.firstName}
{ contact.lastName}
</li>
);
});
}
return contacts;
}
}
和测试文件
jest.mock('TranslatedMessage');
import React = require('react');
import {render} from 'enzyme';
import {user} from '../../../tests/tools';
import {UserProfile} from '../../../app/components/user-profile/user-profile';
describe('UserProfile', () => {
it('should match the snapshot', () => {
const tree = render(<UserProfile user={user} />);
expect(tree).toMatchSnapshot();
});
});
如果您使用 enzyme
,您应该使用浅渲染。
import { shallow } from 'enzyme';
const component = shallow(<UserProfile user={user} />);
expect(component.text()).toMatchSnapshot();
您也可以使用 react-test-renderer
:
import renderer from 'react-test-renderer';
const component = renderer.create(<UserProfile user={user} />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
相信那种感觉。
您正确地使用了快照测试,但您已经到了需要将大型组件分解为较小组件的地步。将它们分开将允许您模拟子组件,这将减少您的快照大小(每个快照,而不是聚合)并使您的差异更容易查看和修复。
例如,而不是:
export class UserProfile extends Component {
public render() {
return (
<div className="ext-admin-user-infos-details">
<div className="ext-admin-user-infos-details-content">
<div className="row">
<div className="col-md-12">
<h3>{this.props.contact.firstName } {this.props.contact.lastName}</h3>
<p className="ext-subtitle">
<span className="ext-minor">{this.props.contact.id}</span>
</p>
</div>
</div>
// ...
</div>
</div>
)
}
}
你做到了:
export class UserProfile extends Component {
public render() {
return (
<div className="ext-admin-user-infos-details">
<div className="ext-admin-user-infos-details-content">
<div className="row">
<div className="col-md-12">
<UserProfileName
first={this.props.contact.firstName}
last={this.props.contact.firstName}
contactId={this.props.contact.id}
/>
</div>
</div>
// ...
</div>
</div>
)
}
}
export class UserProfileName extends Component {
public render() {
return (
<div>
<h3>{this.props.contact.first} {this.props.contact.last}</h3>
<p className="ext-subtitle">
<span className="ext-minor">{this.props.contact.contactId}</span>
</p>
</div>
);
}
}
请注意,我已将呈现用户名的逻辑移至另一个组件。这是更新的测试,模拟子组件:
jest.mock('TranslatedMessage');
jest.mock('UserProfileName'); // Mock this and other children
import React = require('react');
import {render} from 'enzyme';
import {user} from '../../../tests/tools';
import {UserProfile} from '../../../app/components/user-profile/user-profile';
describe('UserProfile', () => {
it('should match the snapshot', () => {
const tree = render(<UserProfile user={user} />);
expect(tree).toMatchSnapshot();
});
});
这个快照将比全部放在一个组件中小得多。当然,您也会对子组件进行测试:
import React = require('react');
import {render} from 'enzyme';
import {UserProfileName} from '../../../app/components/user-profile/user-profile-name';
describe('UserProfileName', () => {
it('should match the snapshot with all props', () => {
const tree = render(<UserProfile first="Test" last="Testerson" contactId="test-id" />);
expect(tree).toMatchSnapshot();
});
it('should render without a first name', () => {
const tree = render(<UserProfile last="Testerson" contactId="test-id" />);
expect(tree).toMatchSnapshot();
});
it('should render without a last name', () => {
const tree = render(<UserProfile first="Test" contactId="test-id" />);
expect(tree).toMatchSnapshot();
});
});
请注意,在这些测试中,我在最后添加了两个案例。当您像这样分解组件时,更容易理解和测试子组件的特定用例!
最后,这种方法的另一个好处是现在您拥有了一个知道如何呈现用户名的可重用组件!您可以概括这一点,并在需要时将其放入。
您的测试是正确的,但是您绝对应该将您的组件分成多个较小的组件,因为它现在太大了。
使用 enzyme
时,您 might not want always to use render
for testing,因为它使输出如此之大。例如,当快照测试纯组件时,您应该使用 shallow
.
我们正在使用 react-test-rendered
for snapshot testing,它比酶更轻。
我对我的组件之一使用 jest 快照测试,生成的快照文件很大(199Kb 和 4310 行)。当快照测试失败时,所有快照文件都会打印到控制台(即渲染 3-4 秒),这给了我这种 "you're doing something wrong" 的感觉。
所以我的问题是:我是否正确使用快照测试?
组件代码:
import _ = require('lodash');
import React = require('react');
import {TranslatedMessage} from 'translator';
import {UserProfile} from './user-profile';
import {ICustomerProfile} from '../customer/customer-profile';
interface IUserProfile {
firstName: string;
lastName: string;
id: string;
customer: ICustomerProfile;
job: string;
email: string;
contacts: string;
phoneNumber: string;
}
interface IUserProfileProps {
contact: IUserProfile;
}
interface IUserProfileState {}
export class UserProfile extends React.Component<IUserProfileProps, IUserProfileState> {
constructor(props: IUserProfileProps) {
super(props);
}
public render(): JSX.Element {
return (
<div className="ext-admin-user-infos-details">
<div className="ext-admin-user-infos-details-content">
<div className="row">
<div className="col-md-12">
<h3>{this.props.contact.firstName } {this.props.contact.lastName}</h3>
<p className="ext-subtitle">
<span className="ext-minor">{this.props.contact.id}</span>
</p>
</div>
</div>
<div className="row">
<div className="col-md-8">
<div className="ext-admin-user-infos-card">
<h6>
<TranslatedMessage messageKey="common.labels.customer" />
</h6>
<ul>
<li>{this.props.contact.customer.name}</li>
</ul>
</div>
<div className="ext-admin-user-infos-card">
<h6>
<TranslatedMessage messageKey="admin.contact.infos.job" />
</h6>
<ul>
<li>{this.props.contact.job}</li>
</ul>
</div>
<div className="ext-admin-user-infos-card">
<h6>
<TranslatedMessage messageKey="admin.contact.infos.email" />
</h6>
<ul>
<li>{this.props.contact.email}</li>
</ul>
</div>
</div>
<div className="col-md-4">
<div className="ext-admin-user-infos-card">
<h6>
<TranslatedMessage messageKey="common.labels.followed" />
</h6>
<ol>
{this.renderContacts(this.props.contact.contacts)}
</ol>
</div>
<div className="ext-admin-user-infos-card">
<h6>
<TranslatedMessage messageKey="common.labels.phone" />
</h6>
<ul>
<li>{this.props.contact.phoneNumber}</li>
</ul>
</div>
</div>
</div>
</div>
</div>
);
}
protected renderContacts(contacts: IUserProfile[]): JSX.Element[] {
let contacts= [];
if (sales) {
_.map(sales, function(contact: IUserProfile): void {
salesContact.push(
<li>
{ contact.firstName}
{ contact.lastName}
</li>
);
});
}
return contacts;
}
}
和测试文件
jest.mock('TranslatedMessage');
import React = require('react');
import {render} from 'enzyme';
import {user} from '../../../tests/tools';
import {UserProfile} from '../../../app/components/user-profile/user-profile';
describe('UserProfile', () => {
it('should match the snapshot', () => {
const tree = render(<UserProfile user={user} />);
expect(tree).toMatchSnapshot();
});
});
如果您使用 enzyme
,您应该使用浅渲染。
import { shallow } from 'enzyme';
const component = shallow(<UserProfile user={user} />);
expect(component.text()).toMatchSnapshot();
您也可以使用 react-test-renderer
:
import renderer from 'react-test-renderer';
const component = renderer.create(<UserProfile user={user} />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
相信那种感觉。
您正确地使用了快照测试,但您已经到了需要将大型组件分解为较小组件的地步。将它们分开将允许您模拟子组件,这将减少您的快照大小(每个快照,而不是聚合)并使您的差异更容易查看和修复。
例如,而不是:
export class UserProfile extends Component {
public render() {
return (
<div className="ext-admin-user-infos-details">
<div className="ext-admin-user-infos-details-content">
<div className="row">
<div className="col-md-12">
<h3>{this.props.contact.firstName } {this.props.contact.lastName}</h3>
<p className="ext-subtitle">
<span className="ext-minor">{this.props.contact.id}</span>
</p>
</div>
</div>
// ...
</div>
</div>
)
}
}
你做到了:
export class UserProfile extends Component {
public render() {
return (
<div className="ext-admin-user-infos-details">
<div className="ext-admin-user-infos-details-content">
<div className="row">
<div className="col-md-12">
<UserProfileName
first={this.props.contact.firstName}
last={this.props.contact.firstName}
contactId={this.props.contact.id}
/>
</div>
</div>
// ...
</div>
</div>
)
}
}
export class UserProfileName extends Component {
public render() {
return (
<div>
<h3>{this.props.contact.first} {this.props.contact.last}</h3>
<p className="ext-subtitle">
<span className="ext-minor">{this.props.contact.contactId}</span>
</p>
</div>
);
}
}
请注意,我已将呈现用户名的逻辑移至另一个组件。这是更新的测试,模拟子组件:
jest.mock('TranslatedMessage');
jest.mock('UserProfileName'); // Mock this and other children
import React = require('react');
import {render} from 'enzyme';
import {user} from '../../../tests/tools';
import {UserProfile} from '../../../app/components/user-profile/user-profile';
describe('UserProfile', () => {
it('should match the snapshot', () => {
const tree = render(<UserProfile user={user} />);
expect(tree).toMatchSnapshot();
});
});
这个快照将比全部放在一个组件中小得多。当然,您也会对子组件进行测试:
import React = require('react');
import {render} from 'enzyme';
import {UserProfileName} from '../../../app/components/user-profile/user-profile-name';
describe('UserProfileName', () => {
it('should match the snapshot with all props', () => {
const tree = render(<UserProfile first="Test" last="Testerson" contactId="test-id" />);
expect(tree).toMatchSnapshot();
});
it('should render without a first name', () => {
const tree = render(<UserProfile last="Testerson" contactId="test-id" />);
expect(tree).toMatchSnapshot();
});
it('should render without a last name', () => {
const tree = render(<UserProfile first="Test" contactId="test-id" />);
expect(tree).toMatchSnapshot();
});
});
请注意,在这些测试中,我在最后添加了两个案例。当您像这样分解组件时,更容易理解和测试子组件的特定用例!
最后,这种方法的另一个好处是现在您拥有了一个知道如何呈现用户名的可重用组件!您可以概括这一点,并在需要时将其放入。
您的测试是正确的,但是您绝对应该将您的组件分成多个较小的组件,因为它现在太大了。
使用 enzyme
时,您 might not want always to use render
for testing,因为它使输出如此之大。例如,当快照测试纯组件时,您应该使用 shallow
.
我们正在使用 react-test-rendered
for snapshot testing,它比酶更轻。