Vue Test Unit入门

1,034

官方测试工具

特别注意要使用版本2.0.0以上文档地址

作用:提供特定的方法,在隔离的环境下进行组件的挂载,以及一系列测试

配置开发环境

如果在创建项目时选择了单元测试一下该步骤可以忽略

在配置之前我们先要了解vue-cli有如下特点:

  • vue-cli是基于插件开发的,插件可以:
    • 安装对应的依赖
    • 修改内部配置
    • 注入命令
  • 在现有项目中安装插件
    • vue add xxx

安装插件

vue add unit-jest 插件地址unit-jest

执行完成后我们会发现在项目package.json中会多出如下依赖包,已及script脚本,并在根目录下生成tests文件夹和jest.config.js

image.png

image.png

image.png

image.png

插件运行过程

  • 安装的依赖
    • vue-test-unit
    • vue-jest
    • 注入了新的命令
  • vue-cli-server test:unit
    • any files in tests/unit that end in .spec.(js|jsx|ts|tsx)
    • any (js(x) | ts(x))files inside __tests__ directories
  • vue-jest转换
    • 将vue SFC文件格式转换为对应的ts文件
    • 将ts通过presets/typescript-babel转换成对应的js文件

开始测试

开始测试之前我们需要了解如下知识点:

  • 渲染组件
    • mountshallowMount
    • 传递属性
  • 元素是否成功的显示
    • 查找元素的不同写法
    • get,getAl
    • find,findAll
    • findComponent,getComponent
  • 触发事件
    • trigger
  • 测试界面是否更新
    • 特别注意dom更新是一个一步过程,注意在测试中使用asyn,await 有点jquery的感觉了

测试用例

先来个简单的例子,判断一个元素的文本内容,以及触发点击事件后的处理。

判断文本内容

1.创建一个test.vue文件,代码如下,现在我们要测试<p class="msg">{{ msg }}</p>的内容,和触发addCount事件后<h1 class="number">{{ count }}</h1>的文本内容

<template>
  <div>
      <p class="msg">{{ msg }}</p>
      <h1 class="number">{{ count }}</h1>
      <button @click="addCount">ADD</button>
  </div>
</template>

<script lang='ts'>
import { ref } from 'vue'
interface DataProps {}
export default {
    name: 'Test',
    props: {
        msg: {
            type: String,
            default: ''
        }
    },
    setup() {
        const count = ref<number>(0)
        const addCount = ()=> {
            count.value++
        }
        return {
            count,
            addCount
        }
    }
  };
</script>

接下来写测试用例,在tests/unit下创建test.spec.ts文件

import Test from '@/components/test.vue'
import {  mount, shallowMount } from '@vue/test-utils'
describe('test.vue',()=> {
    it('renders props.msg when passed',()=>{
        const msg = "new msg"
        // 获取到组件实例
        const  wrapper = mount(Test, {
            // 实例中的传递的props 
            props: {
                msg
            }
        })
        // 获取组件的html文本
        console.log(wrapper.html())
        // case
        expect(wrapper.get('.msg').text()).toBe(msg)

    })
})
  1. 首先引入Test组件,调用@vue/test-utils中的mount方法,获取到test.vue组件实例对象
  2. 我们希望在<p class="msg">{{ msg }}</p>的文本为new msg
  3. 启动测试命令npm run test:unit -- --watch,注意后面的 -- --watch是jest自带的类似webpack的--wacth
  4. 看一下测试的结果如下:

image.png 已经通过了,我们改一下case为expect(wrapper.get('.msg').text()).toBe('222'),发现未通过

image.png 说明这个测试用例是ok的

接下来我们触发点击事件

触发点击事件

describe('test.vue',()=> {
   
    it('renders props.msg when passed', ()=>{
        ...
        // 触发点击事件
        wrapper.get('.addCount').trigger('click')
        expect(wrapper.get('.number').text()).toBe('1')
    })
})

上面代码首先触发点击事件,然后获取期望值,因为是count.value++此时的count应该是1,但实际情况却如下图,难道是test.vue代码中的addCount没触发吗?但是运行项目发现是可以触发addCount的,所以可能情况就是出现了异步,这个时候首先想到了async,await,代码稍加处理下如下:

image.png

describe('test.vue',()=> {
   
    it('renders props.msg when passed', async ()=>{
        ...
        // 触发点击事件
        await wrapper.get('.addCount').trigger('click')
        expect(wrapper.get('.number').text()).toBe('1')
    })
})

再次查看控制台如下图:

image.png

更新表单

<template>
  <div>
      <p class="msg">{{ msg }}</p>
      <h1 class="number">{{ count }}</h1>
      <button class="addCount" @click="addCount">ADD</button>
      <!-- 表单 -->
      <input type="text" v-model="inputValue" /> 
      <!-- 触发添加 -->
      <button @click="addTodo"  class="addTodo">添加数据</button>
      <!-- 渲染列表 -->
      <ul>
        <li v-for="(item, index) in list" :key="index">
           {{ item }}
        </li>
      </ul>
  </div>
</template>

<script lang='ts'>
import { ref } from 'vue'
interface DataProps {}
export default {
    name: 'Test',
    props: {
        msg: {
            type: String,
            default: ''
        }
    },
    setup(props, context) {
        const count = ref<number>(0)
        const inputValue = ref<string>('')
        const list = ref<string[]>([])
        const addCount = ()=> {
            count.value++
        }
        const addTodo = () => {
          if(inputValue.value) {
            list.value.push(inputValue.value)
            context.emit('send', inputValue.value)
          }
        }
        return {
            count,
            addCount,
            inputValue,
            addTodo
        }
    }
  };
</script>

流程如下:

  1. 输入框输入文本内容
  2. 输入后点击添加数据按钮push到list
  3. 在页面进行渲染 开始编写测试用例:
describe('test.vue',()=> {
   
    it('renders props.msg when passed',async ()=> {
        const msg = "new msg"
        const  wrapper = mount(Test, {
            props: {
                msg
            }
        })
        // mock一条数据
        const value = 'inputValue'
        // 写入到input中
        await wrapper.get('input').setValue(value)
        // 验证input的value  input使用
        expect(wrapper.get('input').element.value).toBe(value)
        // 触发点击addTodo事件
        await wrapper.get('.addTodo').trigger('click')
        // 获取所有渲染的li的长度
        expect(wrapper.findAll('li')).toHaveLength(1)
        // 判断第一个li的text
        expect(wrapper.get('li').text()).toBe(value)
    })
})

可以看到所有的测试用例都过了如下图:

image.png

验证事件

在组件中我们会有一些发射事件的处理,先看一下代码,我们在点击按钮的发射一个send事件,并传入 inputValue.value

export default {
    name: 'Test',
    // 添加send
    emits: ['send'],
    setup(props, context) {
        ...
        const addTodo = () => {
          if(inputValue.value) {
            ...
            context.emit('send', inputValue.value)
          }
        }
        ....
    }
  };

接下来开始写测试用例:

it('renders props.msg when passed',async ()=> {
        const msg = "new msg"
        const  wrapper = mount(Test, {
            props: {
                msg
            }
        })
        ...
        // 判断发射事件是否存在
        expect(wrapper.emitted()).toHaveProperty('send')
        const events = wrapper.emitted('send')
        // 判断传入的参数
        expect(events ? events[0]: []).toEqual([value])

        
//      2. 输入后点击`添加数据`按钮push到`list`中
    
//      3. 在页面进行渲染

    })

结果如下图,表明已通过

image.png

异步请求

<template>
    <div>
    ...
        <button class="loadUser" @click="loadUser">load</button>
        <p v-if="user.loading" class="loading">Loading</p>
        <div v-else class="userName">{{user.data && user.data.username}}</div>
        <p v-if="user.error" class="error">error!</p>
    ...
    </div>
</template>
<script lang="ts">
import { defineComponent, watchEffect, ref, reactive } from "vue";
import  userLoader from '@/hooks/useLoader'
import axios from "axios";
import Hello from './hello.vue'
interface PostProps {
  count: number,
  errorCode: number,
  message: string,
  result: boolean,
  data?: [
    {
      agentId: string,
      agentName: string,
      id: string
    }
  ]
}
export default defineComponent({
  ...
  setup(props,context) {
    const user = reactive({
      data: null as any,
      loading: false,
      error: false
    })
    const loadUser = () => {
      user.loading = true
      axios.get('https://jsonplaceholder.typicode.com/users/1').then(resp => {
        console.log(resp)
        user.data = resp.data
      }).catch(() => {
        user.error = true
      }).finally(() => {
        user.loading = false
      })
    }
    return {
      user,
      loadUser
    };
  },
});
</script>

流程也很简单首先点击button按钮,然后触发axios请求,最后渲染到页面上

开始测试用例编写:

import { shallowMount, mount, VueWrapper } from '@vue/test-utils'
// 引入axios
import axios from 'axios'
// flush-promises 会清除所有等待完成的 Promise 具柄
import flushPromises from  'flush-promises'
//模拟axios
jest.mock('axios')
// 很重要的一步,断言成真正的mock类型同时保留axios的方法
const mockAxios = axios as jest.Mocked<typeof axios>

describe('axios', ()=> {
    it('test axios', async ()=> {
    mockAxios.get.mockResolvedValueOnce({ data: {username: 'xxxx'} })
    // 点击
    await wrapper.get('.loadUser').trigger('click')
    // 判断请求是否被调用
    expect(mockAxios.get).toHaveBeenCalled()
    // 判断按钮是否显示
    expect(wrapper.find('.loading').exists()).toBeTruthy()
    // 界面更新完毕
    await flushPromises()
    // 判断请求成功后<p class="loading">Loading</p>是否存在
    expect(wrapper.find('.loading').exists()).toBeFalsy()
    // 判断<div class="userName"></div>最终文本是否是xxxx
    expect(wrapper.get('.userName').text()).toBe('xxxx')

  })
})

好了,基本的介绍就先到了。

写在最后

最后,推荐一套TS全系列的教程吧。 近期在提升TS,收藏了一套很不错的教程,无偿分享给xdm www.yidengxuetang.com/pub-page/in…