常用技巧
明白要测试的是什么
对于 UI 组件来说,我们不推荐一味追求行级覆盖率,因为它会导致我们过分关注组件的内部实现细节,从而导致琐碎的测试。
取而代之的是,我们推荐把测试撰写为断言你的组件的公共接口,并在一个黑盒内部处理它。一个简单的测试用例将会断言一些输入 (用户的交互或 data 的改变) 提供给某组件之后是否导致预期结果 (渲染结果或触发自定义事件)。
比如,对于每次点击按钮都会将计数加一的 Counter
组件来说,其测试用例将会模拟点击并断言渲染结果会加 1。该测试并没有关注 Counter
如何递增数值,而只关注其输入和输出。
该提议的好处在于,即便该组件的内部实现已经随时间发生了改变,只要你的组件的公共接口始终保持一致,测试就可以通过。
这个话题的细节在 Matt O'Connell 一份非常棒的演讲 中有更多的讨论。
浅渲染
在测试用例中,我们通常希望专注在一个孤立的单元中测试组件,避免对其子组件的行为进行间接的断言。
额外的,对于包含许多子组件的组件来说,整个渲染树可能会非常大。重复渲染所有的子组件可能会让我们的测试变慢。
San Test Utils 允许你通过 shallowAttach 方法只挂载一个组件而不渲染其子组件 (即保留它们的存根):
import {shallowAttach} from 'san-test-utils';
const wrapper = shallowAttach(component);
wrapper.vm; // 挂载的 San 实例
断言触发的事件
每个挂载的包裹器都会通过其背后的 San 实例自动记录所有被触发的事件。你可以用 wrapper.fired()
方法取回这些事件记录。
wrapper.vm.fire('foo');
wrapper.vm.fire('foo', 123);
/*
`wrapper.fired()` 返回以下对象:
{
foo: [[], [123]]
}
*/
同样的,也可以通过 wrapper.dispatch()
方法来触发消息的发送。
wrapper.vm.dispatch('foo');
wrapper.vm.dispatch('foo', 123);
/*
`wrapper.dispatched()` 返回以下对象:
{
foo: [[], [123]]
}
*/
然后你可以基于这些数据来设置断言:
// 断言事件已经被触发
expect(wrapper.fired().foo).toBeTruthy()
// 断言事件的数量
expect(wrapper.fired().foo.length).toBe(2)
// 断言事件的有效数据
expect(wrapper.fired().foo[1]).toEqual([123]);
你也可以调用 wrapper.firedByOrder() 获取一个按触发先后排序的事件数组。
从子组件触发事件
你可以通过访问子组件实例来触发一个自定义事件
待测试的组件
const childComponent = san.defineComponent({
name: 'childComponent',
template: `<div />`
});
const parentComponent = san.defineComponent({
name: 'parentComponent',
components: {
child-component: childComponent
},
initData() {
return {
emitted: false
}
},
handleCustom() {
this.data.set('emitted', true);
},
template: `<div>
<child-component on-custom="handleCustom" />
<p s-if="emitted"><div class="emitted" /></p>
</div>`
});
测试代码
import {shallowAttach} from 'san-test-utils';
import parentComponent from './parentComponent';
import childComponent from './childComponent';
describe('parentComponent', () => {
it("contains class 'emitted!' when custom event is emitted", done => {
const wrapper = attach(parentComponent);
wrapper.find(childComponent).vm.fire('custom');
san.nextTick(() => {
expect(wrapper.html()).toContain('<div class="emitted"></div>');
done();
});
});
})
操作组件数据
你可以在包裹器上用 setData
方法直接操作组件状态:
wrapper.setData({count: 10});
数据注入
import {attach} from 'san-test-utils';
const data = {
foo: 'bar',
bar: 123
};
attach(component, {
data
});
存根组件
你可以使用 stubs
选项覆写已经注册的组件:
import {attach} from 'san-test-utils';
attach(component, {
// 将会把 globally-registered-component 解析为空的存根
stubs: ['globally-registered-component']
});