异步行为
您可能已经注意到本指南的其他部分在调用wrapper上的一些方法时使用了await,例如trigger和setValue。这是怎么回事?
您可能知道Vue是被动更新的:当您更改值时,DOM会自动更新以反映最新的值。Vue异步执行这些更新。相比之下,像Jest这样的测试运行程序是同步运行的。这可能会在测试中产生一些令人惊讶的结果。
让我们看看一些策略,以确保Vue在运行测试时按预期更新DOM。
一个简单的例子-使用触发器更新
让我们重新使用事件处理中的<Counter>组件,只需进行一次更改;我们现在在template中呈现count。
const Counter = {
template: `
<p>Count: {{ count }}</p>
<button @click="handleClick">Increment</button>
`,
data() {
return {
count: 0,
};
},
methods: {
handleClick() {
this.count += 1;
},
},
};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");
});test("increments by 1", () => {
const wrapper = mount(Counter);
wrapper.find("button").trigger("click");
expect(wrapper.html()).toContain("Count: 1");
});令人惊讶的是,这次失败了!原因是尽管计数增加了,但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");
});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");
});现在测试将通过,因为我们确保下一个“tick”已经执行,并且在断言运行之前DOM已经更新。
由于await nextTick() 很常见,Vue Test Utils提供了一个快捷方式。导致DOM更新的方法,如trigger和setValue返回nextTick,因此您可以直接等待它们:
test("increments by 1", async () => {
const wrapper = mount(Counter);
await wrapper.find("button").trigger("click");
expect(wrapper.html()).toContain("Count: 1");
});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 mock了你的axios HTTP客户端:
jest.spyOn(axios, "get").mockResolvedValue({ data: "some mocked data!" });jest.spyOn(axios, "get").mockResolvedValue({ data: "some mocked data!" });在这种情况下,Vue不知道未解析的Promise,因此调用nextTick将不起作用——您的断言可能会在解析之前运行。对于这样的场景,Vue Test Utils公开flushPromises,这会导致所有未完成的承诺立即得到解决。
让我们看一个例子:
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.
});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,则必须将该组件装入Suspendse组件中(就像在应用程序中使用它时一样)。
例如,此异步组件:
test("Async component", async () => {
const TestComponent = defineComponent({
components: { Async },
template: "<Suspense><Async/></Suspense>",
});
const wrapper = mount(TestComponent);
await flushPromises();
// ...
});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 Test Utils中的 flushPromises来解决来自非Vue依赖项(如 API 请求)的任何未解决的异步。 - 使用
Suspense可以在异步测试函数中测试具有异步setup组件。
zerone