我知道,对许多开发人员来说,测试似乎是一种浪费时间。你讨厌它,对吗?但是,你应该吗?如果你想创建一个可靠的应用程序,你应该测试你的组件吗?
我将告诉你我的想法:测试你的组件(重要的是,以正确的方式进行测试)是你可以做出的最好的投资之一,如果你正在建立一个长期的东西。但为什么呢?
在本指南中,我将回答这些问题,并通过分享发生在我身上的几个故事来总结用Vue Test Utils测试你的Vue.js组件的好处。
我们将涵盖以下内容。
为什么要测试Vue.js组件?
当你将代码推送到生产中时,你不希望引入错误。如果你是一个有才华的开发者,对你的代码库了如指掌,你可能不会(话虽如此,我也见过很多优秀的、自信的工程师引入了他们没有想到的罕见状况)。
但是,当你因为公司的发展而被大量的工作压得喘不过气来,需要雇佣一些后辈来不断改进产品时,会发生什么?他们会引入bug吗?可能比你想象的更频繁。
当一个初级开发人员推出的东西破坏了你最重要的功能之一,在一个完美的世界里,你会希望在它进入生产服务器之前得到通知。如果你的代码库经过正确的测试,其中一个测试会失败,你就能在任何损害发生之前修复这个问题。
这是一个重要的原因,如果你正在建立一个长期的项目,你应该测试你的代码库:开发人员在一个团队中工作,必须相互保护。一些公司甚至通过在工作流程中引入测试驱动开发(TDD)等方法来改变他们的编码方式。简而言之,这意味着你在对业务逻辑进行编码之前先写好测试(即规格)。
你应该测试你的组件是否正常工作的另一个原因是,这样做可以为每个组件提供文档。通过阅读测试(我们将在接下来的章节中演示),我们可以看到对于一个给定的输入(一个道具,一个事件等),我们可以期待什么输出。而且,正如你可能已经知道的那样,优秀的文档会使调试更容易。
但是如果你问我最喜欢测试的是什么,那就是重构可以变得多么富有成效。几年前,当我开始我独特的网络开发之路,我很快了解到,代码库不是静态的,随着时间的推移会有很多变化。换句话说,你必须每周重构它的某些部分。
我记得当产品经理要求我在一个最关键的界面中引入一个子功能。对我来说,不幸的是,这需要对许多组件进行彻底的重构才能使其发挥作用。我很担心会破坏一些东西,但这种担心很快就消失了。在我完成重构之后,我非常高兴地看到所有的测试都通过了,没有触发任何错误。
信心是关键!事实上,这是测试你的Vue.js组件的另一个好处。当你确信你的代码在正常工作时,你就可以确信你没有运送坏的软件。
如果你仍然不相信,这里有更多的思考:修复问题通常比预防问题的成本高得多。编写测试的时间是值得的。
你应该如何测试Vue.js组件?
谈论一下我们应该测试什么是至关重要的。对于UI组件,我不建议把目标放在测试每一行代码上。这可能会导致过分关注组件的内部实现(即,达到100%的测试覆盖率)。
相反,我们应该编写测试,断言组件的公共接口,并将其作为一个内部黑箱。一个测试案例将断言,提供给组件的一些输入(用户操作、道具、存储)会导致预期的输出(组件渲染、vue事件、函数调用等)。
另外,去年,我在Vue阿姆斯特丹看了Sarah Dayan的一个很棒的演讲,题目是 "用Vue.js进行测试驱动开发"。在她的一张幻灯片中,她说,为了确定你是否应该测试你的一个组件(或其中的一个功能),你必须问自己:如果它改变了,我是否关心这个?换句话说,如果有人破坏了它,它是一个会在接口中引起问题的功能吗?如果是的话,你应该写一个测试来加强你的代码。
什么是Vue Test Utils?
现在我们来谈谈房间里的大象。什么是Vue Test Utils?
Vue Test Utils是一个官方的辅助函数库,帮助用户测试他们的Vue.js组件。它提供了一些方法来装载Vue.js组件并与之隔离互动。我们把这称为包装器。但什么是包装器?
包装器是对安装组件的一种抽象。它提供了一些实用的功能,使我们的生活更容易,比如当我们想触发一个点击或一个事件时。我们会用它来执行一些输入(用户操作、道具、存储变化等),这样我们就可以检查输出是否正确(组件渲染、Vue事件、函数调用等)。
难能可贵的是,如果你在封装器上没有你需要的东西,你可以用wrapper.vm 来抓取Vue实例。所以你有很大的灵活性。
你可以在官方的Vue Test Utils文档中找到包装器上的所有属性和方法。
Vue Test Utils还允许嘲讽和存根组件用shallowMount 或单独的存根来渲染,但我们稍后会说到。所以,是的,这是一个非常完整和可靠的库,你会喜欢。
用Vue Test Utils测试Vue.js组件
现在是我们动手的时候了,开始用Vue Test Utils测试我们的组件。
设置基础设施
你可以选择两个测试运行器。Jest或Mocha和Chai。在本教程中,我们将选择Jest,因为推荐使用Vue Test Utils与Jest。
如果你不熟悉Jest,它是一个由Facebook开发的测试运行器。它的目的是提供一个包含电池的单元测试解决方案。
如果你使用Vue CLI来构建你的项目,这里是你如何在你当前的Vue应用中设置Vue Test Utils。
如果是手动安装,请遵循文档中的安装指南。
bash
vue add unit-jest
npm install --save-dev @vue/test-utils
现在你应该看到一个新的命令被添加到package.json ,我们将用它来运行我们的测试。
json
{
"scripts": {
"test:unit": "vue-cli-service test:unit"
}
}
测试我们的HabitComponent
现在是时候创建我们的第一套测试了。对于我们的例子,我们将创建一个习惯跟踪器。它将由一个单独的组件组成,我们将其命名为Habit.vue ,每次我们完成习惯时,我们都会勾选它。在你的组件文件夹中,复制/粘贴下面的代码。
vue
<template>
<div class="habit">
<span class="habit__name">{{ name }}</span>
<span :class="{ 'habit__box--done': done }" class="habit__box" @click="onHabitDone">
<span v-if="done">✔</span>
</span>
</div>
</template>
<script>
export default {
name: "Habit",
props: {
name: {
type: String,
required: true,
},
},
data: () => ({
done: false,
}),
methods: {
onHabitDone() {
this.done = !this.done;
},
},
};
</script>
<style>
.habit {
height: 100vh;
width: 100%;
display: flex;
text-align: center;
justify-content: center;
align-items: center;
text-transform: uppercase;
font-family: ui-sans-serif, system-ui;
}
.habit__name {
font-weight: bold;
font-size: 64px;
margin-right: 20px;
}
.habit__box {
width: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
border: 4px solid #cbd5e0;
background-color: #ffffff;
font-size: 40px;
cursor: pointer;
border-radius: 10px;
}
.habit__box--done {
border-color: #22543d;
background-color: #2f855a;
color: white;
}
</style>
该组件接受一个道具(习惯的标题),并包括一个方框,当我们点击它时,该方框会变成绿色(即,习惯已经完成)。
在你项目根部的tests 文件夹中,创建一个Habit.spec.js 。我们将在里面编写我们所有的测试。
让我们从创建包装器对象开始,写出我们的第一个测试。
javascript
import { mount } from "@vue/test-utils";
import Habit from "@/components/Habit";
describe("Habit", () => {
it("makes sure the habit name is rendered", () => {
const habitName = "Learn something new";
const wrapper = mount(Habit, {
propsData: {
name: habitName,
},
});
expect(wrapper.props().name).toBe(habitName);
expect(wrapper.text()).toContain(habitName);
});
});
如果你运行npm run test:unit ,你应该看到所有的测试都成功了。
bash
> vue-cli-service test:unit
PASS tests/unit/Habit.spec.js
Habit
✓ makes sure the habit name is rendered (11ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.135s
Ran all test suites.
现在让我们确保我们的习惯在点击盒子的时候被选中。
javascript
it("marks the habit as completed", async () => {
const wrapper = mount(Habit, {
propsData: {
name: "Learn something new",
},
});
const box = wrapper.find(".habit__box");
await box.trigger("click");
expect(box.text()).toContain("✔");
});
注意测试必须是异步的,而且触发器需要被等待。查看Vue Test Utils文档中的 "Testing Asynchronous Behavior"(测试异步行为)一文,了解为什么这是必要的,以及测试异步场景时需要考虑的其他事项。
bash
> vue-cli-service test:unit
PASS tests/unit/Habit.spec.js
Habit
✓ makes sure the habit name is rendered (11ms)
✓ marks the habit as completed (10ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 1.135s
Ran all test suites.
我们还可以验证,当我们点击onHabitDone 方法时被调用。
javascript
it("calls the onHabitDone method", async () => {
const wrapper = mount(Habit, {
propsData: {
name: "Learn something new",
},
});
wrapper.setMethods({
onHabitDone: jest.fn(),
});
const box = wrapper.find(".habit__box");
await box.trigger("click");
expect(wrapper.vm.onHabitDone).toHaveBeenCalled();
});
运行npm run test:unit ,一切都应该是绿色的。
这是你在终端中应该看到的情况。
bash
> vue-cli-service test:unit
PASS tests/unit/Habit.spec.js
Habit
✓ makes sure the habit name is rendered (11ms)
✓ marks the habit as completed (10ms)
✓ calls the onHabitDone method (2ms)
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 1.135s
Ran all test suites.
我们甚至可以检查,当我们改变一个道具时,该组件的行为是否符合预期。
javascript
it("updates the habit method", async () => {
const wrapper = mount(Habit, {
propsData: {
name: "Learn something new",
},
});
const newHabitName = "Brush my teeth";
await wrapper.setProps({
name: newHabitName,
});
expect(wrapper.props().name).toBe(newHabitName);
});
这是你在终端中应该看到的情况。
bash
> vue-cli-service test:unit
PASS tests/unit/Habit.spec.js
Habit
✓ makes sure the habit name is rendered (11ms)
✓ marks the habit as completed (10ms)
✓ calls the onHabitDone method (2ms)
✓ updates the habit method (2ms)
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 1.135s
Ran all test suites.
为了帮助你更快地编码,这里有我最常用的封装方法。
wrapper.attributes():返回封装的DOM节点属性对象wrapper.classes(): 返回封装的DOM节点类wrapper.destroy():销毁一个Vue组件实例wrapper.emitted(): 返回一个包含由封装器vm发出的自定义事件的对象wrapper.find(): 返回第一个DOM节点或Vue组件匹配选择器的Wrapperwrapper.findAll(): 返回一个WrapperArray。wrapper.html(): 返回Wrapper DOM节点的HTML字符串。wrapper.isVisible(): 断言Wrapper是可见的。wrapper.setData(): 设置Wrapper vm数据wrapper.setProps(): 设置Wrapper vm props并强制更新wrapper.text(): 返回Wrapper的文本内容wrapper.trigger(): 在包装器的DOM节点上异步触发一个事件
使用方法fetch
如果你在你的组件中使用fetch 方法来调用API,你会得到一个错误。这里你可以确保fetch 在你的测试中被定义。
bash
npm install -D isomorphic-fetch
然后更新你的package.json 。
json
{
"scripts": {
"test:unit": "vue-cli-service test:unit --require isomorphic-fetch"
}
}
mount 与。shallowMount
你可能会发现,有些人在使用shallowMount ,而不是mount 。原因是,像mount ,它创建了一个封装器,包含了安装和渲染的Vue.js组件,但有存根的子组件。
这意味着该组件将被更快地渲染,因为它的所有子组件都不会被计算。不过要小心,如果你想测试与子组件相连的东西,这种方法会导致一些麻烦。
我们从哪里开始呢?
Vue Test Utils文档是一个很好的资源,可以帮助你开始工作--特别是指南,每个月都会更新。包含所有封装方法的页面和Jest API都是很好的资源,你也应该把它们收藏起来。
记住,为你的项目练习和编写你的测试是开始学习的最好方法。我希望这个指南能帮助你掌握测试你的组件是多么强大。而且,这并不是很难。
我们将以著名的计算机科学家Donald Knuth的一句话来结束本指南:"计算机擅长遵循指令,但不擅长阅读你的思想"。
我很乐意阅读你的评论和你的Twitter消息@RifkiNada。如果你对我的工作感到好奇,你可以在NadaRifki.com查看。
The postTesting Vue.js components with Vue Test Utilsappeared first onLogRocket Blog.