Vue 组件单元测试究竟测试什么?

关于 Vue 组件单元测试最多见的问题就是“我究竟应该测试什么?”前端

虽然测试过多或过少都是可能的,但个人观察是,开发人员一般会测试过头。毕竟,没有人愿意本身的组件未经测试从而致使应用程序在生产中崩溃。vue

在本文中,我将分享一些用于组件单元测试的指导原则,这些指导原则能够确保在编写测试上不会花费大量时间,可是能够提供足够的覆盖率来避免错误。vuex

本文假设你已经了解 Jest 和 Vue Test Utils。bash

示例组件

在学习这些指导原则以前,咱们先来熟悉下要测试的示例组件。组件名为Item.vue ,是 eCommerce App 里的一个产品条目。 微信

Example component

下面是组件的源码。注意有三个依赖项:Vuex ($store), Vue Router ($router) 和 Vue Auth ($auth)。app

Item.vueide

<template>
  <div>
    <h2>{{ item.title }}</h2>
    <button @click="addToCart">Add To Cart</button>
    <img :src="item.image"/>
  </div>
</template>
<script>
export default {
  name: "Item",
  props: [ "id" ],
  computed: {
    item () {
      return this.$store.state.find(
        item => item.id === this.id
      );
    }
  },
  methods: {
    addToCart () {
      if (this.$auth.check()) {
        this.$store.commit("ADD_TO_CART", this.id);
      } else {
        this.$router.push({ name: "login" });
      }
    }
  }
};
</script>

复制代码

配置 Spec 文件

下面是测试用的 spec 文件。其中,咱们将用 Vue Test Utils “浅挂载”示例组件,所以引入了相关模块以及咱们要测试的 Item 组件。函数

同时还写了一个工厂函数用于生成可覆盖的配置对象,以避免在每一个测试中都须要指定 props 和 mock 三个依赖项。 item.spec.js单元测试

import { shallowMount } from "@vue/test-utils";
import Item from "@/components/Item";

function createConfig (overrides) {
  const id = 1;
  const mocks = {
    // Vue Auth
    $auth: {
      check: () => false
    },
    // Vue Router
    $router: {
      push: () => {}
    },
    // Vuex
    $store: {
      state: [ { id } ],
      commit: () => {}
    }
  };
  const propsData = { id };
  return Object.assign({ mocks, propsData }, overrides);
}

describe("Item.vue", () => {
  // Tests go here
});

复制代码

肯定业务逻辑

对于要测试的组件,要问的第一个也是最重要的问题是“业务逻辑是什么”,即组件是作什么的?学习

对于这个Item.vue,业务逻辑是:

  • 根据接收的id属性展现条目信息
  • 若是用户是访客,点击 Add to Cart 按钮将重定向到登陆页
  • 若是用户已登陆,点击 Add to Cart 按钮会触发 Vuex mutation  ADD_TO_CART

肯定输入和输出

当你对组件作单元测试时,可将其视为一个黑盒。方法、计算属性等内部逻辑只影响输出。

所以,下一个重点是肯定组件的输入和输出,由于这些也是测试的输入和输出。

Item.vue 的输入是:

  • id 属性
  • 来自 Vuex 和 Vue Auth 的数据状态
  • 用户点击按钮

输出是:

  • 渲染后的 HTML
  • 发送到 Vuex mutation 或者 Vue Router push 的数据

有些组件也会将表单和事件做为输入,触发事件做为输出。

测试 1: 访客点击按钮跳转路由

有一个业务逻辑是“若是用户是访客,点击 Add to Cart 按钮将重定向到登陆页”。咱们来写这个测试。

咱们经过“shallow mount”组件来编写测试,而后找到并点击Add to Cart 按钮。

test("router called when guest clicks button", () => {
  const config = createConfig();
  const wrapper = shallowMount(Item, config);
  wrapper
    .find("button")
    .trigger("click");
  // Assertion goes here
}

复制代码

随后咱们会加上 assertion。

不要超出输入和输出的界限

在这个测试中很容易采起的作法是在点击按钮后判断路由是否跳转到了登陆页,好比:

import router from "router";

test("router called when guest clicks button", () => {
  ...
  // 错!
  const route = router.find(route => route.name === "login");
  expect(wrapper.vm.$route.path).toBe(route.path);
}

复制代码

虽然这确实也能测试组件的输出,可是它依赖于路由功能,这不该该是组件所关心的。

直接测试组件的输出会更好,也就是调用了 $router.push。至于路由是否最终完成了操做,这已经超出了本测试的范畴。

所以咱们能够监听路由的 push 方法,并断言它是否被登陆路由对象调用。

import router from "router";

test("router called when guest clicks button", () => {
  ...
  jest.spyOn(config.mocks.$router, "push");
  const route = router.find(route => route.name === "login");
  expect(spy).toHaveBeenCalledWith(route);
}

复制代码

测试 2: 登陆用户点击按钮后调用 vuex

接下来让咱们测试业务逻辑“若是用户已登陆,点击 Add to Cart 按钮将触发 Vuex mutation ADD_TO_CART”。

一样,你不须要判断 Vuex 状态是否更改了。要验证这个须要另外单独测试 Vuex store。

组件的职责只是执行 commit,所以咱们只要测试这个动做就行。

首先重写 $auth.check 假数据让它返回 true (模拟登陆用户)。而后监听 store 的 commit 方法,并断言点击按钮后被调用。

test("vuex called when auth user clicks button", () => {
  const config = createConfig({
    mocks: {
      $auth: {
        check: () => true
      }
    }
  });
  const spy = jest.spyOn(config.mocks.$store, "commit");
  const wrapper = shallowMount(Item, config);
  wrapper
    .find("button")
    .trigger("click");
  expect(spy).toHaveBeenCalled();
}

复制代码

不要测试其余库的功能

Item 组件展现条目数据,特别是标题和图片。或许咱们应该写一个测试来专门检查这些?好比:

test("renders correctly", () => {
  const wrapper = shallowMount(Item, createConfig());
  // Wrong
  expect(wrapper.find("h2").text()).toBe(item.title);
}

复制代码

这又是一个没必要要的测试,由于它只是测试了 Vue 从 Vuex 中提取数据并插入到模板的能力。Vue 这个库已经对该机制进行了测试,因此你应该依赖于它。

测试 3: 正确地渲染

可是等等,若是有人不当心将title重命名为name,而后忘记更新插值表达式怎么办?这难道不须要测试吗?

没错,可是若是你像这样来测试模板的方方面面,什么时候才是个头?

测试 HTML 最好的办法是使用快照,用来检查总体渲染后的结果。这不只覆盖了标题插值,还包括图片、按钮文本、任何 class 等。

test("renders correctly", () => {
  const wrapper = shallowMount(Item, createConfig());
  expect(wrapper).toMatchSnapshot();
});

复制代码

其余不须要测试的点还有这些:

  • src 属性是否绑定到 img 元素
  • 添加到 Vuex store 中的数据是否跟插入的数据一致
  • 计算属性是否返回了正确的数据
  • 执行 router push 是否重定向到正确的页面

诸如此类。

总结

我认为上面三个简单的测试对这个组件来讲足够了。

组件单元测试的一个好理念是先假设测试是没必要要的,除非被证实是必要的。

你能够问本身如下问题:

  • 这是业务逻辑的一部分吗?
  • 这是直接测试组件的输入和输出吗?
  • 这是测试本身的代码,仍是第三方代码?

让咱们愉快地单元测试吧!

原文:vuejsdevelopers.com/2019/08/26/…
翻译:1024译站

更多前端技术干货尽在微信公众号:1024译站

微信公众号:1024译站
相关文章
相关标签/搜索