用Vue Test Utils测试Vue.js组件

336 阅读10分钟

我知道,对许多开发人员来说,测试似乎是一种浪费时间。你讨厌它,对吗?但是,你应该吗?如果你想创建一个可靠的应用程序,你应该测试你的组件吗?

我将告诉你我的想法:测试你的组件(重要的是,以正确的方式进行测试)是你可以做出的最好的投资之一,如果你正在建立一个长期的东西。但为什么呢?

在本指南中,我将回答这些问题,并通过分享发生在我身上的几个故事来总结用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测试我们的组件。

设置基础设施

你可以选择两个测试运行器。JestMocha和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>

Learn Something New Unchecked

Learn Something New Checked

该组件接受一个道具(习惯的标题),并包括一个方框,当我们点击它时,该方框会变成绿色(即,习惯已经完成)。

在你项目根部的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组件匹配选择器的Wrapper
  • wrapper.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.