Skip to content

与 VTU 的边界

基于 @testing-library/vue v8 编写

速查

  • 注入:render(C, { global: { plugins: [pinia, router] } })(透传 VTU 的 global)
  • TL:用户中心、隐藏内部、行为测试;VTU:组件中心、暴露内部、契约测试
  • 二者互补:测用户交互用 TL,测 emitted / props 合约用 VTU
  • a11y:jest-axe / vitest-axe 做自动可访问性扫描

render 注入依赖

renderglobal 选项与 Vue Test Utils 完全一致,可注入 Pinia / Vue Router:

ts
import { render, screen } from "@testing-library/vue";
import { createPinia } from "pinia";
import { createRouter, createWebHistory } from "vue-router";

render(App, {
  props: { id: 1 },
  global: { plugins: [createPinia(), router] },
});

// 路由初始导航异步,用 findBy 等待
expect(await screen.findByText("首页内容")).toBeInTheDocument();

render 还有 container(自定义挂载容器,如 <table> 场景)、baseElement 选项。

TL vs VTU

维度Testing LibraryVue Test Utils
哲学用户中心、操作 DOM组件中心、访问内部
查询语义(role/label/text)CSS 选择器、组件引用
内部访问主动隐藏 vm/propswrapper.vm / props() / emitted() 暴露
交互user-event / fireEventtrigger / setValue
插槽较弱完整支持
适合集成 / 行为测试单元 / 契约测试

互补使用

ts
// 验证用户行为 → TL
test("点击提交后显示成功", async () => {
  const user = userEvent.setup();
  render(Form);
  await user.click(screen.getByRole("button", { name: "提交" }));
  expect(screen.getByText("成功")).toBeInTheDocument();
});

// 验证组件契约(emitted 事件)→ VTU
import { mount } from "@vue/test-utils";
test("emit submit 带正确 payload", async () => {
  const wrapper = mount(Form);
  await wrapper.find("button").trigger("click");
  expect(wrapper.emitted("submit")?.[0]).toEqual([{ username: "" }]);
});

TL 自身也有 emitted()(来自 VTU 别名),需要时可用:

ts
const { emitted } = render(MyComponent);
await fireEvent.click(screen.getByRole("button"));
expect(emitted()).toHaveProperty("myEvent");

该用哪个

关注"用户能否完成操作、看到什么"——用 TL;关注"组件 props/emits/slots 契约"——用 VTU。同一项目可混用。

可访问性测试(延伸)

getByRole 已天然推动可访问的 HTML;要系统性扫描 WCAG 规则,加 jest-axe / vitest-axe

ts
import { axe, toHaveNoViolations } from "jest-axe";
expect.extend(toHaveNoViolations);

test("无 a11y 违规", async () => {
  const { container } = render(MyForm);
  expect(await axe(container)).toHaveNoViolations();
});