Skip to content

异步行为

您可能已经注意到本指南的其他部分在调用wrapper上的一些方法时使用了await,例如triggersetValue。这是怎么回事?

您可能知道Vue是被动更新的:当您更改值时,DOM会自动更新以反映最新的值。Vue异步执行这些更新。相比之下,像Jest这样的测试运行程序是同步运行的。这可能会在测试中产生一些令人惊讶的结果。

让我们看看一些策略,以确保Vue在运行测试时按预期更新DOM

一个简单的例子-使用触发器更新

让我们重新使用事件处理中的<Counter>组件,只需进行一次更改;我们现在在template中呈现count

js
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是否在增加:

js
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

js
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更新的方法,如triggersetValue返回nextTick,因此您可以直接等待它们:

js
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客户端:

js
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,这会导致所有未完成的承诺立即得到解决。

让我们看一个例子:

js
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组件中(就像在应用程序中使用它时一样)。

例如,此异步组件:

js
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的函数(如triggersetValue)返回nextTick,因此需要await它们。
  • 使用Vue Test Utils中的 flushPromises来解决来自非Vue依赖项(如 API 请求)的任何未解决的异步。
  • 使用Suspense可以在异步测试函数中测试具有异步setup组件。