在做单元测试中最重要的就是编写单元测试用例。vue 项目的单元测试要测试什么呢? 今天主要来介绍下如何编写 vue3 组件的测试用例
vitest 为我们提供了几个常用的 Api
- describe() 使用 describe 你可以在当前上下文中定义一个新套件,作为一组相关的测试或基准以及其他嵌套套件
- test() 定义了一组关于测试期望的方法。它接收测试名称和一个含有测试期望的函数。
- expect() 用来创建断言,描述测试期望
测试组件渲染
Vue Test Utils 提供两种渲染方式,mount 和 shallowMount。区别是 mount 会渲染子组件,shallowMount 把子组件渲染为 stub 组件。
假如单元测试用例不涉及子组件功能测试的话,使用 shallowMount 更合理。
describe("测试示例", () => {
test("hello world", () => {
const wrapper = shallowMount(HelloWorld);
expect(wrapper.find(".name").text()).toBe("hello world!");
});
});
由于不涉及子组件 此处用 shallowMount 更为合理
测试条件渲染
v-if
const Nav = {
template: `
<nav>
<a v-if="admin" id="admin" href="/admin">Admin</a>
</nav>
`,
data() {
return { admin: false };
},
};
describe("v-if测试示例", () => {
test("v-if", () => {
const wrapper = shallowMount(Nav);
const admin = wrapper.find("#admin");
expect(admin.exists()).toBe(false); // 期望admin节点不存在
});
});
这里用 find()来查找这个节点 用 exists()来判断这个节点是否存在
v-show
const Nav = {
template: `
<nav>
<a id="user" href="/profile">My Profile</a>
<ul v-show="shouldShowDropdown" id="user-dropdown">
<!-- dropdown content -->
</ul>
</nav>
`,
data() {
return { shouldShowDropdown: false };
},
};
describe("v-show测试示例", () => {
test("v-show", () => {
const wrapper = mount(Nav);
expect(wrapper.get("#user-dropdown").isVisible()).toBe(false);
});
});
isVisible()提供检查隐藏元素节点的能力,它能够检查以下内容
- 元素或其祖先元素拥有 display: none 或 visibility: hidden 或 opacity :0 样式
- 元素或其祖先元素在折叠的 details 标签中
- 元素或其祖先元素拥有 hidden 属性
测试组件的 props
<template>
<div class="len">{{ minLength }}</div>
</template>
<script setup lang="ts">
const props = defineProps({
minLength: {
type: Number,
default: 0,
},
});
</script>
describe("props测试示例", () => {
test("props", () => {
const wrapper = shallowMount(Nav, {
props: {
minLength: 10,
},
});
expect(wrapper.find(".len").text()).toBe("10");
});
});
通过传递 props 的值在测试用例中使用
测试组件的 emit 事件
<template>
<button @click="handleClick">Increment</button>
</template>
<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);
const emit = defineEmits(["increment"]);
const handleClick = () => {
count.value += 1;
emit("increment", count.value);
};
</script>
import Counter from "./counter.vue";
describe("emit事件测试示例", () => {
test("emits", () => {
const wrapper = shallowMount(Counter);
wrapper.get("button").trigger("click");
expect(wrapper.emitted()).toHaveProperty("increment");
});
});
当有 emit 事件触发时 wrapper.emitted()返回一个包含该事件名称作为属性的对象
{
increment: [ [ 1 ] ],
click: [ [ [MouseEvent] ] ]
}
测试组件的 slot
<template>
<div>
<slot></slot>
<slot name="header"></slot>
<slot name="scoped" v-bind="msg"></slot>
</div>
</template>
describe("slot测试示例", () => {
test("slot", () => {
const wrapper = shallowMount(Layout, {
slots: {
// 默认插槽
default: "message",
// 具名插槽
header: "123",
},
});
expect(wrapper.text()).toContain("message");
expect(wrapper.text()).toContain("123");
});
// 作用域插槽
test("scoped slot", () => {
const wrapper = shallowMount(Layout, {
slots: {
scoped: `<template #scoped="scope">
Hello {{ scope.msg }}
</template>
`,
},
});
expect(wrapper.text()).toContain("Hello world");
});
});
测试 provide
const Provider = {
setup() {
provide("foo", 1);
return () => h(Middle);
},
};
const Middle = {
render: () => h(Consumer),
};
const Consumer = {
setup() {
const foo = inject("foo");
return () => h("div", { class: "inject-result" }, foo);
},
};
describe("provide测试示例", () => {
test("provides correct data", () => {
const wrapper = mount(Provider);
expect(wrapper.find(".inject-result").text()).toBe("1");
});
});
构造爷孙组件利用 provide/inject 传参
表单测试
<template>
<div>
<input type="email" v-model="email" />
<button @click="submit">Submit</button>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const email = ref("");
const emit = defineEmits(["submit"]);
const submit = () => {
emit("submit", email.value);
};
</script>
describe("表单测试示例", () => {
test("登录", async () => {
const wrapper = shallowMount(Login);
const input = wrapper.find("input");
await input.setValue("my@mail.com");
expect(input.element.value).toBe("my@mail.com");
await wrapper.find("button").trigger("click");
expect(wrapper.emitted("submit")?.[0][0]).toBe("my@mail.com");
});
});
通过 setValue 设置一个文本控件或 select 元素的值并更新 v-model 绑定的数据。
忽略某些代码
istanbul 提供注释语法,允许某些代码不计入覆盖率。
// 忽略一个 if 分支
/* istanbul ignore if */
if (hardToReproduceError)) {
return callback(hardToReproduceError);
}
// 忽略一个 else 分支
/* istanbul ignore else */
if (foo.hasOwnProperty("bar")) {
// do something
}
// 忽略默认值 {}
var object = parameter || /* istanbul ignore next */ {};
/* istanbul ignore file */
// 写在文件头部 忽略整个文件
参考文章: