![[wip]Vue3+Jest 单元测试 - 图1](/uploads/projects/xinbao37@roadmap/d0dfcffe8889305439c938dcd3808dc6.jpeg)
shadow of person’s hand holding flowers - Photo by Tanya Trofymchuk on Unsplash(https://unsplash.com/photos/gzXhH-RiydU)
0 按
本文是 《Vue自动化测试:导读》的后续篇章,一些基础概念会被忽略。
本文把握的核心:
- 配合
jest+@vue/test-utils@2+vue3,完成技术的选型 -
1 安装
新创建项目,可以在
unit-test中选择jest- 已创建好的项目,可以通过
vue add unit-jest来一键完成引入 - 手动引入,需要考虑下列依赖,这些不是很重要,我就隐藏了,想看自己拖动看。
```bash
相关依赖
npm i @vue/test-utils@next vue-jest@next ts-jest typescript -D截至 2021-06-20 @vue/test-utils 是 2.0.0-rc.6
当前 vue-jest@5
具体的 preset 可以自行查看https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-plugin-unit-jest/README.md
jest初始化可以参考:
npx jest —init
<a name="5RpiF"></a>## Jest之前jest不支持 esm,所以需要babel来转。<a name="9mBrh"></a># 2 Jest 使用一个简单的demo:```javascripttest('demo',() => {expect(1+1).toBe(2);})
jest最佳实践:
- 合理使用层级,但不需要特别深
- describe > it > test ,实际中不用分到 test
- describe 对应
suites - it 对应 tests
- 合理使用js判断,减少断言库api的使用,降低认知成本
- 看下面
实例 2-1 - 断言库常用的就几种,看
实例2-2和思维导图
- 看下面
- 遇到有关timer异步任务
- 尽可能只使用 useFakeTimers 配合 useAllTimer ,见
实例2-3
- 尽可能只使用 useFakeTimers 配合 useAllTimer ,见
- 遇到异步Promise情况,比如fetch:
- 一律使用 async/await
- 网络请求一律走mock,接口测试独立运行,见
实例2-4
下面是实例2-1,减少对api的依赖:
expect(1+1).toBe(2)// 可优化成,一个判断是否相等expect(1+1===2).toBe(true)expect(2).toBeGreaterThan(1)// 可优化成js判断expect(2>1).toBe(true)
下面是实例2-2,常用的对比方法:
expect(.1+.2).toBeCloseTo(0.3,5)expect([1,2]).toEqual([1,2]) // 对象类型使用 equalexpect({a:{b:1}}).toEqual({a:{b:1}})expect({a:undefined,b:2}).toEqual({b:2}) // 这里注意expect({a:unefined,b:2}).not.toStrictEqual({b:2})// 严格相等
下面是常用api一览,不全够用:
下面是实例2-3,timer不等待:
let i = 0;export const fnSetTimeout = (fn) => {setTimeout(() => {fn({ name: "otto" });i = 1;}, 1000);};jest.useFakeTimers();it("测试异步函数", () => {const fn = jest.fn();fnSetTimeout(fn);jest.runAllTimers();expect(i === 1).toBe(true);});
下面是实例2-4,mock异步请求
// api.jsimport axios from "axios";export const fetchUser = ()=>{return axios.get('/user')}// 测试这个函数会发出真正的请求,不必要
解决方法:
- 创建同级目录
__mocks__/api.js,文件同名,填充对应的内容:
有了mock文件,这样就简单了,记住接口测试是独立的一个测试方向。 ```javascript jest.mock(‘./api.js’); // 会自动使用同级目录mocks/api.jsexport const fetchUser = ()=>{return new Promise.resolve({user:'otto'}))}// 这个方法和真实fetch形式一致,注意返回体可能需要包裹。
import {fetchList,} from ‘./api’; // 引入mock的方法 it(‘fetchUser测试’,async ()=>{ let data = await fetchUser(); expect(data).toEqual({user:’otto’}) })
注意,直接mock文件有时候不如重写 axios.get ,比如考虑```javascript// __mocks__/axios.jsexport default {get(url){return new Promise((resolve,reject)=>{if(url === '/user'){resolve({user:'otto'});}})}}
也是一种思路。以上实例2-4结束。
jest config
类似这样,具体可通过查询 preset 来得到:
module.exports = {moduleFileExtensions: [ // 测试的文件类型'js','jsx','json','vue'],transform: { // 转化方式'^.+\\.vue$': 'vue-jest', // 如果是vue文件使用vue-jest解析'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', // 如果是图片样式则使用 jest-transform-stub'^.+\\.jsx?$': 'babel-jest' // 如果是jsx文件使用 babel-jest},transformIgnorePatterns: [ // 转化时忽略 node_modules'/node_modules/'],moduleNameMapper: { // @符号 表示当前项目下的src'^@/(.*)$': '<rootDir>/src/$1'},snapshotSerializers: [ // 快照的配置'jest-serializer-vue'],testMatch: [ // 默认测试 /test/unit中包含.spec的文件 和__tests__目录下的文件'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'],testURL: 'http://localhost/', // 测试地址watchPlugins: [ // watch提示插件'jest-watch-typeahead/filename','jest-watch-typeahead/testname']}
3 vue-test-utils
vtu 是 vue-test-utils 的简称,有时候在vue内部会这样称呼。
因为 vue3 单文件是 vue后缀,不是普通的js文件,所以需要使用一个插件来完成转化。把转化后的结果交给jest做进一步判断,这个插件就是官方的 vue-test-utils 。
vue3 的 vue-test-utils 文档 https://next.vue-test-utils.vuejs.org/
最佳实践:
- 鼓励moun通过
mount(vue组件)得到实例,可配合 stub 或者 干脆 shallMount
基本api
针对具体的实例:
- setValue 给input设定值
- trigger 触发某个动作
- get(string) 查找dom
- findAll(string) 查找所有dom 后面跟断言 haveLength(2)
- classes() 获得class name 后面跟断言 .toContain(string) 接字符串
- findComponent 嵌套的自定义组件
- .exists() 是否存在 .toBeTruthy()
最佳实践,为了找到某个确定的元素,建议开发时候有意识添加 data-test='xxx'
注意,vue是异步更新dom,一系列的操作会 nexttick 更新,所以 async/await 等待某个操作结束
router vuex 等
有些组件,比如 router-link route-view ,在渲染时候会提示找不到对应组件,因为这是在 app初始化时候挂载的,同理 element-plus 也是。
两种种解决思路:
- 挂载
- 模拟
挂载。就是玩真的,导入对应组件,真的去使用插件。 createLocalVue 这个后面具体说。
模拟。这个词叫 存根stubs ,很奇怪的称呼,作用是忽略指定的组件
假定存在这个组件
<template><div><h1>当前路由:{{this.$route.path}}</h1><router-link to="/">首页</router-link><router-link to="/about">关于页面</router-link><router-view></router-view></div></template>
槽点满满:
- 哪来的 $router,这么一看还是 vue3 的 useRouter 好一些,来源真实
- 哪来的 link view
it("测试Nav组件", () => {let wrapper = shallowMount(Nav,{// 忽略这两个组件stubs:['router-link','router-view'],mocks:{ // mock一些数据传入到Nav组件中$route:{path:'/'}}});expect(wrapper.find('h1').text()).toContain('/')});
妥了,忽略两个全局组件,模拟行为。
实际中我会修改vue提供的 jest.config.js
module.exports = {preset: "@vue/cli-plugin-unit-jest/presets/typescript-and-babel",moduleFileExtensions: ["vue", "ts", "d.ts", "js", "tsx", "json"],testPathIgnorePatterns: ["/node_modules/", "**/node_modules/**/*"],};
参考 第十章, p113附近
