Vue 组件的单元测试

2023-09-18 10:11:29

1、基本的示例

单元测试是软件开发非常基础的一部分。单元测试会封闭执行最小化单元的代码,使得添加新功能和追踪问题更容易。Vue 的单文件组件使得为组件撰写隔离的单元测试这件事更加直接。它会让你更有信心地开发新特性而不破坏现有的实现,并帮助其他开发者理解你的组件的作用。
这是一个判断一些文本是否被渲染的简单的示例:

<template>
  <div>
    <input v-model="username">
    <div
      v-if="error"
      class="error"
    >
      {{ error }}
    </div>
  </div>
</template>
<script>
export default {
  name: 'Hello',
  data () {
    return {
      username: ''
    }
  },
  computed: {
    error () {
      return this.username.trim().length < 7
        ? 'Please enter a longer username'
        : ''
    }
  }
}
</script>
import { shallowMount } from '@vue/test-utils'
import Hello from './Hello.vue'
test('Hello', () => {
  // 渲染这个组件
  const wrapper = shallowMount(Hello)
  // `username` 在除去头尾空格之后不应该少于 7 个字符
  wrapper.setData({ username: ' '.repeat(7) })
  // 确认错误信息被渲染了
  expect(wrapper.find('.error').exists()).toBe(true)
  // 将名字更新至足够长
  wrapper.setData({ username: 'Lachlan' })
  // 断言错误信息不再显示了
  expect(wrapper.find('.error').exists()).toBe(false)
})

上述代码片段展示了如何基于 username 的长度测试一个错误信息是否被渲染。它展示了 Vue 组件单元测试的一般思路:渲染这个组件,然后断言这些标签是否匹配组件的状态。

2、为什么要测试?

组件的单元测试有很多好处:

  • 提供描述组件行为的文档
  • 节省手动测试的时间
  • 减少研发新特性时产生的 bug
  • 改进设计
  • 促进重构自动化测试使得大团队中的开发者可以维护复杂的基础代码

3、实际的例子

单元测试应该:

  • 可以快速运行
  • 易于理解
  • 只测试一个独立单元的工作我们在上一个示例的基础上继续构建,同时引入一个工厂函数 (factory function)使得我们的测试更简洁更易读。这个组件应该:
  • 展示一个“Welcome to the Vue.js cookbook”的问候语
  • 提示用户输入用户名
  • 如果输入的用户名少于七个字符则展示错误信息让我们先看一下组件代码:
<template>
  <div>
    <div class="message">
      {{ message }}
    </div>
    Enter your username: <input v-model="username">
    <div
      v-if="error"
      class="error"
    >
      Please enter a username with at least seven letters.
    </div>
  </div>
</template>
<script>
export default {
  name: 'Foo',
  data () {
    return {
      message: 'Welcome to the Vue.js cookbook',
      username: ''
    }
  },
  computed: {
    error () {
      return this.username.trim().length < 7
    }
  }
}
</script>

我们应该测试的内容有:

  • message 是否被渲染
  • 如果 error 是 true,则
    应该展示
  • 如果 error 是 false,则
    不应该展示我们的第一次测试尝试:
import { shallowMount } from '@vue/test-utils'
import Foo from './Foo.vue'
describe('Foo', () => {
  it('renders a message and responds correctly to user input', () => {
    const wrapper = shallowMount(Foo, {
      data: {
        message: 'Hello World',
        username: ''
      }
    })
    // 确认是否渲染了 `message`
    expect(wrapper.find('.message').text()).toEqual('Hello World')
    // 断言渲染了错误信息
    expect(wrapper.find('.error').exists()).toBeTruthy()
    // 更新 `username` 并断言错误信息不再被渲染
    wrapper.setData({ username: 'Lachlan' })
    expect(wrapper.find('.error').exists()).toBeFalsy()
  })
})

上述代码有一些问题:

  • 单个测试断言了不同的事情
  • 很难阐述组件可以处于哪些不同的状态,以及它该被渲染成什么样子接下来的示例从这几个方面改善了测试:
  • 每个 it 块只做一个断言
  • 让测试描述更简短清晰
  • 只提供测试需要的最小化数据
  • 把重复的逻辑重构到了一个工厂函数中 (创建 wrapper 和设置 username 变量)更新后的测试:
import { shallowMount } from '@vue/test-utils'
import Foo from './Foo'
const factory = (values = {}) => {
  return shallowMount(Foo, {
    data () {
      return {
        ...values
      }
    }
  })
}
describe('Foo', () => {
  it('renders a welcome message', () => {
    const wrapper = factory()
    expect(wrapper.find('.message').text()).toEqual("Welcome to the Vue.js cookbook")
  })
  it('renders an error when username is less than 7 characters', () => {
    const wrapper = factory({ username: ''  })
    expect(wrapper.find('.error').exists()).toBeTruthy()
  })
  it('renders an error when username is whitespace', () => {
    const wrapper = factory({ username: ' '.repeat(7)  })
    expect(wrapper.find('.error').exists()).toBeTruthy()
  })
  it('does not render an error when username is 7 characters or more', () => {
    const wrapper = factory({ username: 'Lachlan'  })
    expect(wrapper.find('.error').exists()).toBeFalsy()
  })
})

注意事项:
在一开始,工厂函数将 values 对象合并到了 data 并返回了一个新的 wrapper 实例。这样,我们就不需要在每个测试中重复 const wrapper = shallowMount(Foo)。另一个好处是当你想为更复杂的组件在每个测试中伪造或存根一个方法或计算属性时,你只需要声明一次即可。

4、额外的上下文

上述的测试是非常简单的,但是在实际情况下 Vue 组件常常具有其它你想要测试的行为,诸如:

  • 调用 API
  • 为 Vuex 的 store,commit 或 dispatch 一些 mutation 或 action
  • 测试用户交互我们在 Vue Test Utils 的教程中提供了更完整的示例展示这些测试。
    Vue Test Utils 及庞大的 JavaScript 生态系统提供了大量的工具促进 100% 的测试覆盖率。单元测试只是整个测试金字塔中的一部分。其它类型的测试还包括 e2e (端到端) 测试、快照比对测试等。单元测试是最小巧也是最简单的测试——它们通过隔离单个组件的每一个部分,来在最小工作单元上进行断言。

快照比对测试会保存你的 Vue 组件的标记,然后比较每次新生成的测试运行结果。如果有些东西改变了,开发者就会得到通知,并决定这个改变是刻意为之 (组件更新时) 还是意外发生的 (组件行为不正确)。

端到端测试致力于确保组件的一系列交互是正确的。它们是更高级别的测试,例如可能会测试用户是否注册、登录以及更新他们的用户名。这种测试运行起来会比单元测试和快照比对测试慢一些。

单元测试中开发的时候是最有用的,即能帮助开发者思考如何设计一个组件或重构一个现有组件。通常每次代码发生变化的时候它们都会被运行。

更多推荐

node.js

前端工程化:开发项目直到上线,过程中集成的所有工具和技术Node.js是独立执行JavaScript代码的环境Node.js环境比浏览器环境中的JS少了BOM和DOMfs模块-读写文件模块:类型插件,封装了方法/属性fs模块:封装了与本机文件系统进行交互的方法/属性语法:加载fs模块对象写入文件内容读取文件内容Comm

自定义指令

一,原生指令v-bind:属性名="变量名"v-on:事件名="函数名"缩写模式::属性名="变量名"@事件名="函数名"示例:<inputtype="text"v-bind:disabled="isDisabled"v-on:change="change"/><inputtype="text":disabled="i

「工具|数据接口」免费公开的REST API & 如何借助github搭建自己的fake API接口

本文主要介绍日常开发、测试、教学或者分享中,可能遇到的模拟数据问题。分享免费开发的测试数据接口,以及如何利用github快速搭建定制化的接口数据,避免使用真实数据的风险以及自己现编数据的麻烦。文章目录一、场景说明二、免费公开的FakeRESTAPI:jsonplaceholder三、借助GitHub和MyJSONSer

手机全自动无人直播系统,成为商家实景无人直播带货好帮手!

商家手机无人直播系统最近太火爆了,那么,这个产品究竟是什么呢?全自动无人直播系统是一款手机自动直播软件,目地在于帮助广大商家和企业实现无人直播卖货,从而解放双手、降低人工干预的需求。当然,无人直播系统除了个人可以使用,拿来直播卖货等,也适合创业者,创业模式一是可以oem贴牌,二是直接进行渠道代理。通过OEM贴牌,用户可

微服务架构介绍

系统架构的演变1、技术架构发展历史时间轴①单机垂直拆分:应用间进行了解耦,系统容错提高了,也解决了独立应用发布的问题,存在单机计算能力瓶颈。②集群化负载均衡可有效解决单机情况下并发量不足瓶颈。③服务改造架构虽然系统经过了垂直拆分,但是拆分之后发现有重复的功能,比如,用户注册、发邮件等等,一旦项目大了,集群部署多了,这些

光伏监控系统在光伏电站运营中的作用及发展

摘要:光伏电站,具体来说便是相连于电网并将电力输送给电网的光伏发电系统,是我国重点和全力发展的绿色能源项目。其中,监控自动化系统的接入,属于光伏电站应用中的重要部分。对于光伏区监控系统的探究,可以使光伏电站接入自动化系统有相应的提升,进而强化电站的运行效率和运维效率,进一步降低运维成本。关键词:光伏监控系统;光伏电站运

【Vue2.0源码学习】生命周期篇-模板编译阶段(template)

文章目录1.前言2.模板编译阶段分析2.1两种$mount方法对比2.2完整版的vm.$mount方法分析3.总结1.前言前几篇文章中我们介绍了生命周期的初始化阶段,我们知道,在初始化阶段各项工作做完之后调用了vm.$mount方法,该方法的调用标志着初始化阶段的结束和进入下一个阶段,从官方文档给出的生命周期流程图中可

GB28181学习(五)——实时视音频点播(信令传输部分)

要求实时视音频点播的SIP消息应通过本域或其他域的SIP服务器进行路由、转发,目标设备的实时视音频流宜通过本域的媒体服务器进行转发;采用INVITE方法实现会话连接,采用RTP/RTCP协议实现媒体传输;信令流程分为客户端主动发起和第三方呼叫控制两种方式,本文主要介绍客户端主动发起的方式;应具有媒体流保活机制;流程客户

第二十七章 Classes - 引用其他类成员

文章目录第二十七章Classes-引用其他类成员引用其他类成员第二十七章Classes-引用其他类成员引用其他类成员在方法中,使用下面的语法来引用其他类成员:要引用ObjectScript中的参数,使用如下表达式:..#PARAMETERNAME只能使用ObjectScript直接访问参数。要从Python访问参数,请

助力工业物联网,工业大数据之服务域:可视化工具Grafana介绍【三十八】

文章目录前言08:可视化工具Grafana介绍09:可视化工具Grafana部署10:Grafana集成Prometheus11:Grafana集成MySQL监控前言项目所需工具:链接:https://pan.baidu.com/s/1sIa8nninf2Fz6YqE3vUpqQ?pwd=5wr3提取码:5wr3–来自

Android 匿名共享内存的使用

注:本文内容转载自如下文章:Android匿名共享内存的使用AndroidView的绘制是如何把数据传递给SurfaceFlinger的呢?跨进程通信时,数据量大于1MB要怎么传递呢?用匿名共享内存(Ashmem)是个不错的选择,它不仅可以减少内存复制的次数,还没有内存大小的限制。这篇文章介绍在Java层如何使用匿名共

热文推荐