浅谈Vue组件单元测试究竟测试什么

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

虽然测试过多或过少都是可能的,但我的观察是,开发人员通常会测试过头。毕竟,没有人愿意自己的组件未经测试从而导致应用程序在生产中崩溃。

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

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

示例组件

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

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

Item.vue

<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 是否重定向到正确的页面

诸如此类。

总结

我认为上面三个简单的测试对这个组件来说足够了。
组件单元测试的一个好理念是先假设测试是不必要的,除非被证明是必要的。

你可以问自己以下问题:

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

让我们愉快地单元测试吧!希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • vue项目中添加单元测试的方法

    从网上找了很多例子关于单元测试,都是如何新建项目的时候的添加单元测试,用vue-cli中怎么添加,但是我的项目已经生成了,不能再一次重新初始化,这时如何添加单元测试,这里面遇到了好多坑,写在这里记录一下心得. 1.用vue-cli生成一个新的项目,把单元测试需要的文件直接复制到你的项目中 vue init webpack vuetest 2.安装Karma+Mocha模块,这个模块依赖比较多,我在遇到了坑,解决问题半天发现缺少了某个模块,在这里全部列出需要的模块 复制代码 代码如下: npm i

  • 如何为vue的项目添加单元测试

    动机 单元测试能避免出现一些代码运行结果与预期不符的错误,通常是一些比较低级但又难以发现的问题. 粗心且懒,在每次调整之后,需要不断地检查代码,反复去走流程.担心由于自己的改动而导致了逻辑上的错误.而这里面的一大部分工作其实可以让单元测试来完成. 有了单元测试之后,可以对代码本身形成一种规范.如果在进行单元测试过程中发现自己的一些代码不方便进行测试,那么你可能需要重新审视这些代码,看是否有一些设计上不合理或者可以优化的地方. 嵌入了单元测试的项目显得更加的专业,也会更有逼格,测试本身是开发环节需

  • 教你如何编写Vue.js的单元测试的方法

    Vue.js是一个JavaScript框架,可用于构建Web应用程序的前端框架.特别是在创建复杂功能时,对于每个项目,有必要在我们的应用程序中查看所有内容,并检查它是否符合预期.然而,对于大型项目,每次新的更新后,检查每个功能将变得很麻烦.因此,我们可以创建可以一直运行的自动化测试,并保证我们的代码可以正常运行.在本文中,我们将为VueJS创建一些简单的单元测试. 要进行测试,我们将先制作一个基本的待办事项列表组件.我们将测试该列表是否正确显示,并且用户可以将新项目添加到待办事项列表中.希望在本

  • 详解使用jest对vue项目进行单元测试

    最近领导对前端提出了新的要求,要进行单元测试.之前使用vue做了一个快报名小程序的pc端页面,既然要做单元测试,就准备用这个项目了,之前有些react的经验,vue还是第一遭 vue-cli3.0单元测试方面更加完备,就先升级到了cli3.0,因为项目是用typescript写的,需要ts-jest,得到jest的配置如下 { "jest": { "moduleFileExtensions": [ "js", "jsx", &

  • vue2单元测试环境搭建

    从网上找了很多例子关于单元测试,都是如何新建项目的时候的添加单元测试,用vue-cli中怎么添加,但是我的项目已经生成了,不能再一次重新初始化,这时如何添加单元测试,这里面遇到了好多坑,写在这里记录一下心得. 1.用vue-cli生成一个新的项目,把单元测试需要的文件直接复制到你的项目中 vue init webpack vuetest 文件下载地址 源代码Github链接 2.安装Karma+Mocha模块,这个模块依赖比较多,我在遇到了坑,解决问题半天发现缺少了某个模块,在这里全部列出需要的

  • 使用Karma做vue组件单元测试的实现

    养成良好的编码习惯,一个合格的程序员需要掌握一些编写单元测试的能力.单元测试也可以整体上提升我们的代码质量,这里介绍下 VUE 组件的单元测试. 如果想直接通过 Demo 学习,可以跳过下面的内容,点击这里下载示例 技术栈 @vue/test-utils[1.0.0-beta.30] istanbul-instrumenter-loader[3.0.1] karma[4.4.1] karma-chrome-launcher[3.1.0] karma-mocha[1.3.0] karma-sour

  • 详解Vue单元测试case写法

    书接上文,karma+webpack搭建vue单元测试环境介绍了vue单元测试环境搭建及查看源文件的测试覆盖覆盖率.今天来说一下vue单元测试思路和case的写法.测试框架使用jasmine,语法参考. 代码地址:https://github.com/MarxJiao/vue-karma-test/tree/spec-demo 测试关注点 对于vue组件,单元测试测试主要关注以下几点: vue组件加载后,各数据模型是否符合预期 定义的方法是否可用 filter是否可用 带有props的组件,数据

  • 对 Vue-Router 进行单元测试的方法

    由于路由通常会把多个组件牵扯到一起操作,所以一般对其的测试都在 端到端/集成 阶段进行,处于测试金字塔的上层.不过,做一些路由的单元测试还是大有益处的. 对于与路由交互的组件,有两种测试方式: 使用一个真正的 router 实例 mock 掉 $route  和 $router  全局对象 因为大多数 Vue 应用用的都是官方的 Vue Router,所以本文会谈谈这个. 创建组件 我们会弄一个简单的 <App>,包含一个  /nested-child  路由.访问 /nested-child

  • 浅谈Vue组件单元测试究竟测试什么

    关于 Vue 组件单元测试最常见的问题就是"我究竟应该测试什么?" 虽然测试过多或过少都是可能的,但我的观察是,开发人员通常会测试过头.毕竟,没有人愿意自己的组件未经测试从而导致应用程序在生产中崩溃. 在本文中,我将分享一些用于组件单元测试的指导原则,这些指导原则可以确保在编写测试上不会花费大量时间,但是可以提供足够的覆盖率来避免错误. 本文假设你已经了解 Jest 和 Vue Test Utils. 示例组件 在学习这些指导原则之前,我们先来熟悉下要测试的示例组件.组件名为 Item

  • 浅谈vue 组件中的setInterval方法和window的不同

    vue组件中,this指向实例,[实例中重写了setInterval等一整套方法].所以,千万不能和 window 下挂载的方法混用 具体不同在于,window.setInterval执行完比后返回一个id,而vue实例中返回[定时器对象],当然该对象中包含一个_id的私有属性 因为 clearInterval 方法参数是id,所以最佳实践是统一使用 window 的方法,不要使用 vue组件的方法 vue中的定时器方法,要使用箭头函数,不要出现 const that = this 的写法 //

  • 浅谈Vue组件及组件的注册方法

    相信在使用Vue进行项目开发的时候很多人会接触到vue组件,最常见的就是我们使用的element-ui组件库,用起来确实很方便,大大减少了我们的开发时间.在一个项目中其实有很多可复用的代码块,如果我们可以把这些内容封装成一个组件就能够很方便的进行各种重复使用. 那么什么是Vue组件呢?它是vue.js最强大的功能之一,是可扩展的html元素,是封装可重用的代码,同时也是Vue实例,可以接受相同的选项对象(除了一些根级特有的选项) 并提供相同的生命周期钩子. 使用组件 组件名大小写 定义组件名的方

  • 浅谈Vue父子组件和非父子组件传值问题

    本文介绍了浅谈Vue父子组件和非父子组件传值问题,分享给大家,具体如下: 1.如何创建组件 1.新建一个组件,如:在goods文件夹下新建goodsList.vue <template> <div class='tmpl'> goodsList组件 </div> </template> <style> </style> <script> export default { data() { return{} }, creat

  • 浅谈vue单一组件下动态修改数据时的全部重渲染

    今天在学习vue的过程中,发现一个有趣的现象. 在某一组件下的某一数据通过点击事件被动态修改的时候,对应view中的数据同步的进行了修改,没错,这不是废话吗,vue的一大特色就是数据的双向绑定.可有趣的是,该组件下我写的另一个用Math.random()的data值对应的值和视图也发生了变化 这就让我这个刚入门的小白有点奇怪了,我修改一个,怎么变了两个????脑洞放开一想,会不会数据在双向同步的时候,发生了什么,比如.是不是只要有一个节点变了,node都重新进行了加载??? 我想这其中的缘由必定

  • 浅谈vue中组件绑定事件时是否加.native

    组件绑定事件时 1. 普通组件绑定事件不能添加.native, 添加后事件失效 2. 自定义组件绑定事件需要添加.native, 否则事件无效 <template> <!-- <mt-field label="用户名" placeholder="请输入用户名"></mt-field> --> <input type="text" @keyup.native="show($event)

  • 浅谈Vue 函数式组件的使用技巧

    什么是函数式组件 没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法,它只是一个接受一些 prop 的函数.简单来说是 一个无状态和无实例的组件 基本写法: Vue.component('my-component', { functional: true, // Props 是可选的 props: { // ... }, // 为了弥补缺少的实例 // 提供第二个参数作为上下文 render: function(createElement, context) { // ... }

  • 浅谈vue中子组件传值的默认值情况

    当父组件中的content值没有传入时,子组件利用default属性设置默认值,此情况时,页面会显示default value. 当传入content的值时,default属性的默认值不生效,界面显示为: 补充知识:Vue父组件向子组件传值遇到的BUG 当子组件中含有props属性,使用ref对其中的prop属性赋值时报错 Avoid mutating a prop directly since the value will be overwritten whenever the parent

  • 浅谈Vue的组件间传值(包括Vuex)

    目录 父传子: 子传父: 在不使用Vuex的情况下,组件间传值的方式是通过父传子的方式或者兄弟组件传值. 父传子: fatherComponent: <template> <div> <HELLOWORLD :needData="content"></HELLOWORLD> </div> </template> <script> import HELLOWORLD from '../components

  • 浅谈Vue入门需掌握的知识

    Vue作为一款目前最流行的前端框架之一,是许多前端开发工程师的不二选择.最近我在前端岗位上也运用Vue实现了几款产品,那么今天来分享一下Vue是什么,以及我对Vue的见解. 一.定义 Vue是一套用于构建用户界面的渐进式JavaScript框架 与传统JS和JQuery框架不同,Vue的渐进式框架表示开发者可以由简单组件写起,渐渐搭建出一个复杂的前端平台. 形成Vue渐进式框架的核心概念为:组件化,MVVM,响应式,和生命周期,下面会一个个详细介绍. 二.为什么要用Vue? 1. 组件化 Vue

随机推荐