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:
```javascript
test('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]) // 对象类型使用 equal
expect({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.js
import 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.js
export 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附近