异步行为
您可能已经注意到指南的其他部分在调用 wrapper
上的一些方法(例如 trigger
和 setValue
)时使用了 await
。这是怎么回事呢?
您可能知道 Vue 是响应式更新的:当您更改一个值时,DOM 会自动更新以反映最新的值。 Vue 异步执行这些更新。相反,像 Jest 这样的测试运行器是同步运行的。这可能会导致测试中出现一些令人惊讶的结果。
让我们看看一些策略来确保 Vue 在运行测试时按预期更新 DOM。
一个简单的例子 - 使用 trigger
更新
让我们重新使用来自 事件处理 的 <Counter>
组件,并进行一个更改;我们现在在 template
中渲染 count
。
const Counter = {
template: `
<p>Count: {{ count }}</p>
<button @click="handleClick">Increment</button>
`,
data() {
return {
count: 0
}
},
methods: {
handleClick() {
this.count += 1
}
}
}
让我们编写一个测试来验证 count
是否在增加
test('increments by 1', () => {
const wrapper = mount(Counter)
wrapper.find('button').trigger('click')
expect(wrapper.html()).toContain('Count: 1')
})
令人惊讶的是,这失败了!原因是虽然 count
增加了,但 Vue 不会在下一个事件循环滴答声之前更新 DOM。因此,断言 (expect()...
) 将在 Vue 更新 DOM 之前被调用。
提示
如果您想了解更多关于这种核心 JavaScript 行为的信息,请阅读有关 事件循环及其宏任务和微任务 的内容。
撇开实现细节不谈,我们如何解决这个问题呢?Vue 实际上提供了一种方法让我们等待 DOM 更新:nextTick
。
import { nextTick } from 'vue'
test('increments by 1', async () => {
const wrapper = mount(Counter)
wrapper.find('button').trigger('click')
await nextTick()
expect(wrapper.html()).toContain('Count: 1')
})
现在测试将通过,因为我们确保在断言运行之前执行了下一个“滴答声”,并且 DOM 已更新。
由于 await nextTick()
很常见,Vue 测试工具提供了一个快捷方式。导致 DOM 更新的方法(例如 trigger
和 setValue
)返回 nextTick
,因此您只需直接 await
它们即可
test('increments by 1', async () => {
const wrapper = mount(Counter)
await wrapper.find('button').trigger('click')
expect(wrapper.html()).toContain('Count: 1')
})
解决其他异步行为
nextTick
用于确保在继续测试之前,响应式数据中的某些更改反映在 DOM 中。但是,有时您可能还想确保其他与 Vue 无关的异步行为也已完成。
一个常见的例子是一个返回 Promise
的函数。也许您使用 jest.mock
模拟了您的 axios
HTTP 客户端
jest.spyOn(axios, 'get').mockResolvedValue({ data: 'some mocked data!' })
在这种情况下,Vue 不知道未解析的 Promise,因此调用 nextTick
将不起作用 - 您的断言可能会在它解析之前运行。对于这种情况,Vue 测试工具公开了 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 () => {
// some component that makes a HTTP called in `created` using `axios`
const wrapper = mount(AxiosComponent)
await flushPromises() // axios promise is resolved immediately
// after the line above, axios request has resolved with the mocked data.
})
提示
如果您想了解更多关于测试组件上的请求的信息,请务必查看 发出 HTTP 请求 指南。
测试异步 setup
如果您要测试的组件使用异步 setup
,那么您必须在 Suspense
组件内挂载该组件(就像您在应用程序中使用它时一样)。
例如,这个 Async
组件
const Async = defineComponent({
async setup() {
// await something
}
})
必须按如下方式测试
test('Async component', async () => {
const TestComponent = defineComponent({
components: { Async },
template: '<Suspense><Async/></Suspense>'
})
const wrapper = mount(TestComponent)
await flushPromises();
// ...
})
注意:要访问 Async
组件的底层 vm
实例,请使用 wrapper.findComponent(Async)
的返回值。由于在这种情况下定义并挂载了一个新组件,因此由 mount(TestComponent)
返回的包装器包含它自己的(空的)vm
。
结论
- Vue 异步更新 DOM;测试运行器则同步执行代码。
- 使用
await nextTick()
来确保在测试继续之前 DOM 已更新。 - 可能更新 DOM 的函数(如
trigger
和setValue
)返回nextTick
,因此您需要await
它们。 - 使用 Vue 测试工具中的
flushPromises
来解析来自非 Vue 依赖项(如 API 请求)的任何未决的 Promise。 - 使用
Suspense
来测试具有异步setup
的组件,并在异步测试函数中测试。