最近 Vue Test Utils 2.0 已出了 Beta 版。1.x 已经很稳定了,它很容易上手,也很适合做单元测试,强烈建议你试一试。本文将讲述一些 Vue Test Utils 的提示、技巧,它们可能是你已经知道的也可能是还不知道的。
使用 await nextTick()
在测试中经常会看到在触发事件后使用 nextTick()
的代码:
async function someTest() {
wrapper = mount(Component)
wrapper.trigger('click')
await wrapper.vm.nextTick()
expect(something).toBe(true)
}
这可以更简洁一些,因为 trigger()
本身就返回 wrapper.vm.nextTick()
(即一个 Promise)。
因此,你可以摆脱 await nextTick()
,只需等待触发的调用:
async function someTest() {
wrapper = mount(Component)
await wrapper.trigger('click')
expect(something).toBe(true)
}
你还可以等待以下其他调用的结果:
- setChecked()
- setData()
- setSelected()
- setProps()
- setValue()
使用 await flushPromises()
1nextTick1 在确保响应式数据的更改在继续测试之前反映在 DOM 中方面非常有用。然而,有时您可能还希望确保其他非 Vue 相关的异步行为也已完成。
一个常见的例子是返回 Promise 的函数。也许您使用 jest.mock
来模拟 axios HTTP 客户端:
jest.spyOn(axios, 'get').mockResolvedValue({ data: 'some mocked data!' })
在这种情况下,Vue 并不知道未解决的 Promise,因此调用 nextTick
不起作用——你的断言可能在 Promise 解决之前运行。对于这种情况,Vue Test Utils 提供了 flushPromises
方法,它可以立即解决所有未完成的Promise。
让我们看一个示例:
import { flushPromises } from '@vue/test-utils'
import axios from 'axios'
jest.spyOn(axios, 'get').mockResolvedValue({ data: 'some mocked data!' })
test('uses a mocked axios HTTP client and flushPromises', async () => {
// 使用 `axios` 在 `created` 中 进行 HTTP 调用的组件
const wrapper = mount(AxiosComponent)
await flushPromises() // axios promise 立即被解决
// 在上面的代码行之后,axios 请求已经用模拟数据进行了解析。
})
一个易于遵循的规则是在诸如trigger
或setProps
的变更时始终使用await
。如果你的代码依赖一些诸如 axios 的异步操作,也要为flushPromises
加入一个await
。
不要在 nextTick() 中使用回调函数
请注意,在 nextTick()
中使用回调函数将意味着错误无法被捕获。
在谈论 nextTick()
的同时,有一个重要的事实需要注意。如果在 nextTick()
中有一个回调函数,并且抛出错误,它将不会显示在测试结果中。
为了解决这个问题,有两件事情可以做——参考以下代码:
// 这不会被发现错误
it('will time out', (done) => {
Vue.nextTick(() => {
expect(true).toBe(false)
done()
})
})
// 下面三个测试将按预期工作
it('will catch the error using done', (done) => {
Vue.config.errorHandler = done
Vue.nextTick(() => {
expect(true).toBe(false)
done()
})
})
it('will catch the error using a promise', () => {
return Vue.nextTick().then(function () {
expect(true).toBe(false)
})
})
it('will catch the error using async/await', async () => {
await Vue.nextTick()
expect(true).toBe(false)
})
去除过渡效果
过渡效果可能会导致测试变得困难。除非你特别测试一些过渡功能,否则通常最好去除过渡效果。
// Stub函数只会渲染子组件
const transitionStub = () => ({
render: function (h) {
return this.$options._renderChildren
}
})
test('should render Foo, then hide it', async () => {
const wrapper = mount(Foo, {
stubs: {
// 去除过渡效果
transition: transitionStub()
}
})
expect(wrapper.text()).toMatch(/Foo/)
})
$route 和 $router 属性是只读的
在使用 Vue Router 进行测试时,请注意如果使用 createLocalVue
并在其上使用 Router,$route
和 $router
属性是只读的。
如果你开始测试涉及到 Vue Router 的内容,通常会使用 createLocalVue:
import { shallowMount, createLocalVue } from '@vue/test-utils'
import VueRouter from 'vue-router'
const localVue = createLocalVue()
localVue.use(VueRouter)
shallowMount(Component, {
localVue
})
请记住,当你这样做时,它会使 $route
(当前路由)和 $router
(路由本身)成为只读属性。如果你已经使用了 localVue.use(VueRouter) 并在挂载时使用它,你将无法设置模拟的路由数据。
以下代码是一个无法工作的示例!
import { shallowMount, createLocalVue } from '@vue/test-utils'
import VueRouter from 'vue-router'
const localVue = createLocalVue()
localVue.use(VueRouter)
const $route = {
path: '/some/path'
}
const wrapper = shallowMount(Component, {
localVue,
mocks: {
// 设置 $route 无效!当使用localVue时,它被设置为只读。
$route
}
})
使用 render() 或 renderToString()
如果在服务器上进行测试并且需要测试渲染后的 HTML,请使用 render()
或 `renderToString()。
render()
在内部使用 vue-server-renderer
将组件渲染为静态 HTML - 这包含在 @vue/server-test-utils
包中。
const wrapper = await render(SomeComponent)
expect(wrapper.text()).toContain('<div></div>')
还有一个 renderToString()
函数。
使用 is() 检查组件的类型
有时候你需要检查 mount()
的输出或者 find()
的结果是否是某种特定的组件类型。为此,你可以使用 is
。
const wrapper = mount(SomeComponent)
expect(wrapper.is(SomeComponent)).toBe(true)
expect(wrapper.find('.modal').is(ModalComponent)).toBe(true)
使用 ref 或 name 查找组件
findComponent()
函数具有按照 Vue 组件 ref
标识符或名称搜索元素的能力。
const wrapper = mount(SomeComponent)
const btn = wrapper.find({ ref: 'myButton' })
const input = wrapper.find({ name: 'myInput' })
使用 config.silent 选项抑制 Vue 的警告信息
你可能会认为不应该这样做。但是为了避免测试中出现有关变更 props 或其他警告的通知,可以使用 config.silent = true
选项。
import { config } from '@vue/test-utils'
config.silent = true
使用 exists()
如果你需要检查 或 WrapperArray 是否为空,可以使用 exists()
const wrapper = mount(ContactForm)
expect(wrapper.exists()).toBe(true)
const buttons = wrapper.findAll('button')
const specificButton = wrapper.find('button.submit-button')
expect(buttons.exists()).toBe(true) // WrapperArray 非空
expect(specificButton.exists()).toBe(true)
使用 getComponent()
findComponent() 可能返回一个空结果。如果你需要确保它找到了内容(否则测试将失败),可以使用 getComponent()
// 如果 `getComponent` 没有找到任何元素将会抛出一个而错误。`findComponent` 则不会做任何事。
expect(wrapper.getComponent(Bar)) // => gets Bar by component instance
expect(wrapper.getComponent({ name: 'bar' })) // => gets Bar by `name`
expect(wrapper.getComponent({ ref: 'bar' })) // => gets Bar by `ref`
data 合并
使用 data 配置进行挂载时,它将与现有的 data 函数合并。
const TheComponent = {
template: '<div>{{ foo }}///{{ bar }}</div>',
data() {
return {
foo: 'COMPONENT_FOO',
bar: 'COMPONENT_BAR'
}
}
}
const wrapper = mount(TheComponent, {
// 将被合并/覆盖的额外数据
data(){
return {
bar: 'FROM_MOUNT_DATA'
}
}
});
expect(wrapper.text()).toBe('COMPONENT_FOO///FROM_MOUNT_DATA')
评论 (0)