下一篇文章
前端自动化测试(二)TDD与BDD 实todoList
什么是前端自动化测试?
前端编写一段js 去运行生产中的js代码,我们预期会出现的结果与实际出现的结果是否相等,在上线前检测成问题,这样通过代码自动检测,而非人肉点击就是前端自动化测试
前端自动化测试的优势
1.更好的代码组织,项目的可维护性增强。
2.更小的Bug出现概率,尤其是回归测试中的Bug。
3.修改工程质量差的项目,更加安全。
4.项目具备潜在的文档特性。
5.扩展前端知识面。
学习自动化测试更有利于读懂各种源码,测试用例其实就是源码的文档,他详细告诉了实现的功能
tips:
下列指定版本只是为了,以后学习不冲突
npm i jest@24.8.0 -D
// 初始化配置
npx jest --init
// 配置启动,加上--watchAll会自动监听所有test文件改动并运行 默认是a模式
// --watch 自动是o模式
// 配置任意一个,就会默认为该模式,同时在watch Usage中消失
"scripts": {
"test": "jest --watchAll"
},
// 按 f 以仅运行失败的测试。
› Press f to run only failed tests.
// 按 o 仅运行与已更改文件相关的测试。
› Press o to only run tests related to changed files.
// 按 p 以按文件名正则表达式模式进行筛选
//只想执行那些测试文件就可以用p模式
› Press p to filter by a filename regex pattern.
// 按 t 以按测试名称 regex 模式进行筛选
// 只想执行那些测试用例就可以用t模式 也叫feilter模式
› Press t to filter by a test name regex pattern.
// 按q退出监视
› Press q to quit watch mode.
// 按u确定更新快照(只在快照时显示)
› Press u to update failing snapshots.
// 按i以交互方式更新失败的快照。
Press i to update failing snapshots interactively.
// 按 s 跳过当前用例
Press s to skip the current test.
// 按 Enter 可触发测试运行。
› Press Enter to trigger a test run.
// 按w展示所有使用方式
Watch Usage: Press w to show more.
当没有改动或者错误的文件时,再次按对应的就能退出该模式
按 o的时候报错
–watch is not supported without git/hg, please use --watchAll
需要在git中使用,通过git来记录修改的文件所以要安装git并初始化git文件同时需要提交到本地
在jest.config.js中 添加
coverageDirectory: "coverage",//生成代码覆盖率报告会放在coverage目录下
会在根目录生成coverage文件夹,点击打开lcov-report 下的 index.html 就能看见图形化界面
cnpm install @babel/core@7.4.5 @babel/preset-env@7.4.5 -D
在根目录新建一个文件.babelrc
{
"presets": [
[
"@babel/preset-env",{
"targets":{
"node":"current"
}
}
]
]
}
我们简单写点测试用例来认识一下jest
新建一个math.js 用来创建 加减方法
export function add(x,y){
return x+y
}
export function minus(x,y){
return x-y
}
export function multi(x,y){
return x*y
}
下面我们就来测试一下这个math.js
//引入需要测试的方法
import {add,minus,multi} from './math'
test('测试加法 3+3',()=>{
//我期望3+3得到6
expect(add(3,3)).toBe(6)
})
test('测试减法 3-3',()=>{
expect(minus(3,3)).toBe(0)
})
test('测试乘法 3*3',()=>{
expect(multi(3,3)).toBe(9)
})
上面的测试是否很简单,没错前端测试就是这样简单明了,
通过代码去执行方法,或模拟用户行为
test原理 简单用原生js来实现一下上述代码
import {add,minus,multi} from './math'
var result = add(3,7);
var expected = 10;
if(result!== expected){
throw Error(`3+7应该等于${expected},但是结果却是${result}`)
}
var result = minus(3,3);
var expected = 0;
if(result!== expected){
throw Error(`3-3应该等于${expected},但是结果却是${result}`)
}
// 实现一个
// expect(add(3,3)).toBe(6)
// expect(minus(6,3)).toBe(3)
function expect (result){
return {
toBe: function(actual){
if(result!==actual){
throw new Error(`预期值和实际值不相等 预期${actual} 结果却是${result}`)
}
}
}
}
// expect(add(3,3)).toBe(6)
// 进一步完善
function test (desc,fn){
try{
fn()
console.log(`${desc}通过测试`);
} catch(e){
console.log(`${desc}没有通过测试 ${e}`);
}
}
test('测试加法 3+3',()=>{
expect(add(3,3)).toBe(6)
})
接下来让我们简单的认识一部分jest的匹配器,
test('测试10与10匹配',()=>{
//toBe 匹配器
//类似 object.is ===
// 只能匹配值,不能匹配对象等引用
expect(10).toBe(10)
})
test ('测试对象内容相等',()=>{
//toEqual 匹配器
// 能匹配值,对象等引用
const a = {one:1};
expect(a).toEqual({one:1})
})
test ('测试内容为null',()=>{
//toBeNull 匹配器
const a = null;
expect(a).toBeNull();
})
test ('测试内容为undefined',()=>{
//toBeUndefined 匹配器
const a = undefined;
expect(a).toBeUndefined();
})
test ('测试内容为defined',()=>{
//toBeDefined 匹配器
const a = null;
expect(a).toBeDefined();
})
test("测试内容为真",()=>{
// toBeTruthy 匹配器
const a = 1;
expect(a).toBeTruthy()
})
test("测试内容为假",()=>{
// toBeFalsy 匹配器
const a = 0;
expect(a).toBeFalsy()
})
test("不为真",()=>{
// not 匹配器取反操作
const a = 1
expect(a).not.toBeFalsy()
})
// 数字相关匹配器
test('count大于9',()=>{
const count = 10
expect(count).toBeGreaterThan(9);
})
test('count小于9',()=>{
// toBeLessThanOrEqual
const count = 8
expect(count).toBeLessThan(9);
})
test('count大于等于9',()=>{
const count = 9
expect(count).toBeGreaterThanOrEqual(9);
})
// js运算错误示例
test("0.1+0.2",()=>{
const a = 0.1
const b = 0.2
// expect(a+b).toEqual(0.3)
/*
Expected: 0.3
Received: 0.30000000000000004
*/
// 对于浮点型计算匹配需要使用
// toBeCloseTo
expect(a+b).toBeCloseTo(0.3)
})
// String 相关匹配器
test("str中包含字符",()=>{
//toMatch 可以是正则表达式
const str = "www.baidu.com"
// expect(str).toMatch('baidu')
expect(str).toMatch(/baid/)
})
// 数组相关匹配器
test("数组中包含某一项",()=>{
const arr = ['a','b','c']
// 可以set后在匹配
expect(arr).toContain('a')
})
// 异常
const throwNewErrorFunc = ()=>{
throw new Error('this is a new error')
}
test('toThorow',()=>{
expect(throwNewErrorFunc).toThrow()
// 如果要填写内容意思就是匹配异常内容相当,也可以是正则表达式
})
大致分为 :
beforeAll 所有测试用例之前
beforeEach 每个测试用例执行前都调用
afterEach 每个测试用例执行之后
afterAll 所有测试用执行之后
下面来测试一下执行顺序
对于归类分组,你可以手动自己分文件,来归类,也可以用describe来分类,
每一个describe都是一个单独的作用域,可以作用于,下面的所有的describe,
同级的互不影响,每个describe都可以拥有独自的钩子函数,执行顺序,先执行外部,再执行内部
新建一个文件counter.js
//模拟用于测试的方法
export default class Counter{
constructor(){
this.number = 0
}
addOne(){
this.number+=1
}
addTwo(){
this.number+=2
}
minusOne(){
this.number-=1
}
minusTwo(){
this.number-=2
}
}
测试该文件 新建 counter.test.js
import Counter from "./counter"
// 相同的归类分组
// 2种方式,一种分文件,一种是用describe分组
describe('测试counter的相关代码',()=>{
console.log('测试counter的相关代码');
let counter = null
beforeAll(()=>{
// 所有测试用例之前
console.log('beforeAll');
})
beforeEach(()=>{
// 每个测试用例执行前都调用
console.log('beforeEach');
counter = new Counter()
})
afterEach(()=>{
// 每个测试用例执行之后
console.log('afterEach');
})
afterAll(()=>{
// 所有测试用例之后
console.log('AfterAll');
})
describe('测试增加的代码',()=>{
beforeEach(()=>{
console.log('beforeEach to add');
})
afterEach(()=>{
console.log('afterEach to add');
})
afterAll(()=>{
console.log('afterAll to add');
})
beforeAll(()=>{
console.log('beforeAll to add');
})
console.log('测试增加的代码');
test('测试Counter中的addOne方法',()=>{
console.log('测试Counter中的addOne方法');
counter.addOne();
expect(counter.number).toBe(1)
})
test('测试Counter中的addTwo方法',()=>{
console.log('测试Counter中的addTwo方法');
counter.addTwo();
expect(counter.number).toBe(2)
})
})
describe('测试减少的代码',()=>{
console.log('测试减少的代码');
test('测试Counter中的minusOne方法',()=>{
console.log('测试Counter中的minusOne方法');
counter.minusOne();
expect(counter.number).toBe(-1)
})
test('测试Counter中的minusTwo方法',()=>{
console.log('测试Counter中的minusTwo方法');
counter.minusTwo();
expect(counter.number).toBe(-2)
})
})
})
// 如果只想执行某一个用例,可以用test.only来修饰 only可以同时存在多个
/*
// 每一个describe都是一个单独的作用域,可以作用于,下面的所有的describe,
同级的互不影响,每个describe都可以拥有独自的钩子函数,执行顺序,先执行外部,再执行内部
*/
/*
执行顺序如下:
console.log counter.test.js:7
测试counter的相关代码
console.log counter.test.js:44
测试增加的代码
console.log counter.test.js:58
测试减少的代码
console.log counter.test.js:12
beforeAll
console.log counter.test.js:42
beforeAll to add
console.log counter.test.js:18
beforeEach
console.log counter.test.js:33
beforeEach to add
console.log counter.test.js:46
测试Counter中的addOne方法
console.log counter.test.js:36
afterEach to add
console.log counter.test.js:23
afterEach
console.log counter.test.js:39
afterAll to add
console.log counter.test.js:18
beforeEach
console.log counter.test.js:60
测试Counter中的minusOne方法
console.log counter.test.js:23
afterEach
console.log counter.test.js:27
AfterAll
*/
安装 axios
npm i axios -D
新建文件 fetchData用于模拟异步代码
import axios from 'axios'
//该接口返回值
//{
// "success": true
//}
//回调类型的异步函数
export const fetchDataCbk = function(fn){
axios.get('http://www.dell-lee.com/react/api/demo.json')
.then(function(response) {
fn(response.data)
})
}
//无回调类型的异步函数
export const fetchData = function(){
return axios.get('http://www.dell-lee.com/react/api/demo.json')
}
测试文件fetchData.test.js
import {fetchData,fetchDataCbk} from "./fetchData"
// 回调类型的异步函数测试
// 只有执行到done执行才结束
test('用done来测试返回结果为{success:true}',(done)=>{
fetchDataCbk((data)=>{
expect(data).toEqual({
success: true
})
done()
})
})
// 无回调类型的异步函数测试
//多种实现方法
test('测试返回结果为{success:true}',()=>{
return fetchData().then((res)=>{
expect(res.data).toEqual({
success:true
})
})
})
test('测试返回结果为{success:true}',async()=>{
const res = await fetchData()
expect(res.data).toEqual({
success:true
})
})
test('测试返回结果为{success:true}',()=>{
return expect(fetchData()).resolves.toMatchObject({
data:{
success: true
}
})
})
test('测试返回结果为{success:true}',async()=>{
await expect(fetchData()).resolves.toMatchObject({
data:{
success: true
}
})
})
//测试返回404
test('测试返回结果为 404',()=>{
expect.assertions(1);//测试用例必须执行一次
return fetchData().catch((e)=>{
expect(e.toString().indexOf('404')!==-1).toBe(true)
})
})
test('测试返回结果为 404',()=>{
return expect(fetchData()).rejects.toThrow()
})
当我们测试请求时,我们并不需要测试接口返回的数据,接口测试是属于后端的测试了,我们只关心,代码是否正常执行
而且如果都去请求,那么测试效率会很慢,这个时候我们就需要用mock来模拟ajax请求,不去请求真实的ajax
新建文件 demo.js
import Axios from "axios"
export const runCallback = function (callBack){
callBack()
}
export const createObject = function (callBack){
new callBack()
}
export const getData = function(){
return Axios.get('/api')
}
测试 demo.test.js
import {runCallback,createObject,getData} from './demo'
import Axios from "axios"
jest.mock('axios') //模拟axios
//回调的异步函数
test('测试runCallback', ()=>{
// 可以自由定义返回值
const func = jest.fn(()=>{
return '456'
})
// 上面等同提出来下面
// func.mockImplementation(()=>{
// return '456'
// })
// 如果只想返回this
// func.mockReturnThis()
// func.mockReturnValueOnce('一次返回')
// func.mockReturnValue('456') 定义返回值
runCallback(func)
// 通过jest.fn 创建的mock函数,可以用toBeCallEd捕获这个函数是否被调用了
expect(func).toBeCalled()
// expect(func).toBeCalledWith('value') 每一次调用传入的都是value
console.log(func.mock);
/*打印的mock:
{
calls: [ [] ], //包含调用时,传递的参数,可以通过判断calls的length来判断调用了几次
instances: [ undefined ],指func 运行时,this的指向
invocationCallOrder: [ 1 ],调用的顺序
results: [ { type: 'return', value: '456' } ] 执行调用的返回值
}
*/
})
test('测试createObject',()=>{
const fn = jest.fn()
createObject(fn)
console.log(fn.mock);
})
test('测试getData',async()=>{
// 模拟返回,不会去请求真实的数据
// mockResolvedValueOnce
Axios.get.mockResolvedValue({data:'hello'})
await getData().then((data)=>{
expect(data).toEqual({data:'hello'})
})
})
我们除开上面的模拟axios的方式,我们还可以通过模拟异步函数,通过使用模拟的异步函数来达到不请求axios的效果
被测试文件 demo.js
import Axios from "axios"
export const fetchData = function(){
return Axios.get('/api')
}
export const getNumber = function(){
return 123
}
在同级创建一个_mocks_
文件夹
同样创建一个demo.js来模拟原demo.js 的异步请求
export const fetchData = ()=>{
return new Promise((resolved,reject)=>{
resolved("(function(){return '123'})()")
})
}
测试文件 demo.test.js
jest.mock('./demo.js') //模拟后会去查找__mocks__下的demo.js,而非真实的的demo.js
// 或者直接将config.js中的automock 改成true 自动开启模拟
// unmok 不模拟
import {fetchData} from './demo'
// 当我们开启模拟时,如果想让模拟文件中异步需要模拟,而同步不需要模拟就需要下面这样引入同步方法
const {getNumber} = jest.requireActual('./demo.js') //引入真正的demo
test('测试异步fetchData',async()=>{
return fetchData().then(data=>{
console.log(data);
expect(eval(data)).toEqual('123')
})
})
test('测试同步getNumber',async()=>{
expect(getNumber()).toBe(123)
})
故名思意,就是类似拍照一样,给你的代码生成一个副本,当代码有所变动,就去与副本中的代码对比,判断是否需要本次的修改,什么时候使用快照呢,当你的代码基本完善,无需修改时,就可以生成一个快照,以后出现代码修改,就可以通过快照的检测,知道那个文件发生了改动
测试文件 demo.js
export const generateConfig= ()=>{
return {
sever:"localhost",
port:8080,
proxy:8081
}
}
export const generateAnotherConfig= ()=>{
return {
sever:"localhost",
port:8080,
proxy:8082
}
}
export const generateTimeConfig= ()=>{
return {
sever:"localhost",
port:8080,
proxy:8084,
time:new Date()
}
}
测试文件 demo.test.js
import {
generateConfig,
generateAnotherConfig,
generateTimeConfig
} from "./demo";
test("测试generateConfig 函数", () => {
expect(generateConfig()).toMatchSnapshot();
});
//假设,测试一个配置文件,如果你修改了配置文件,如果使用的是toEqual(),
/*那么每次修改配置,都需要同步修改test,这样很麻烦,使用toMatchSnapshot()
(快照), 会在根目录生成一个snapshots文件保存运行时,的测试的配置项代码,就
好像,拍了一个照片,之后就会和对比新快照,和旧快照是否一致,判断测试用例是否
通过,
假设这时修改了配置
1 snapshot failed from 1 test suite. Inspect your code changes or press `u` to update them.
你只需打开w操作指令,按u表示,更新快照,就可以再次通过,
类似于提示你,是否要确实这次修改
当出现多个快照时,如果不想所有快照都更新,想一个一个确认更新,这个时候
w 中会多出一个 i 模式,让你进入到一个一个确认下,这时再按u就表示确认更新快照
如果感觉是错的,或者不确定,可以按s跳过该快照用例
*/
test("测试generateAnotherConfig 函数", () => {
expect(generateAnotherConfig()).toMatchSnapshot();
});
// 当配置项中存在new Date() 这种动态变化的参数,就需要配置去忽略它,不然无法通过
test("测试generateTimeConfig 函数", () => {
expect(generateTimeConfig()).toMatchSnapshot({
time: expect.any(Date) //任意date类型都行
});
});
//生成行内快照,下面的object就是运行后生成的快照
// 前置条件需要安装包cnpm i [email protected] -D
// 行内快照 用toMatchInlineSnapshot不会单独生成一个文件,而是把快照直接
// 生成到函数内,
test("测试generateAnotherConfig 函数", () => {
expect(generateAnotherConfig()).toMatchInlineSnapshot(`
Object {
"port": 8080,
"proxy": 8082,
"sever": "localhost",
}
`);
});
当我们测试延时器等等时,不可能去等待时间再执行,这样测试效率会极低,所以jest提供了如下方式来快捷的测试timer
被测试文件 timer.js
export const timer = (callback)=>{
setTimeout(()=>{
callback()
setTimeout(()=>{
callback()
},3000)
},3000);
}
测试文件 timer.test.js
import {timer} from './timer'
beforeEach(()=>{
jest.useFakeTimers(); //每个测试用例执行前,初始一下防止影响
})
test('测试定时器',()=>{
const fn = jest.fn()
timer(fn);
// jest.runAllTimers(); //让定时器立即执行,与上面的use配对使用
// jest.runOnlyPendingTimers(); //只执行队列中存在的timer
jest.advanceTimersByTime(3000)//快进定时器
expect(fn).toHaveBeenCalledTimes(1) //fn只调用一次
jest.advanceTimersByTime(3000) //快进是在上一个的基础上,存在多个测试用例时,可能会印象下面的,所以我们需要在运行之前重置一下
expect(fn).toHaveBeenCalledTimes(2)
})
新建模拟类的测试文件
util.js
export default class Util{
init(){
}
a(){
}
b(){
}
}
新建文件 demo.js
import Util from './util'
const demoFunction = (a,b)=>{
const util = new Util();
util.a(a)
util.b(b)
}
export default demoFunction
测试文件 demo.test.js
import demoFunction from './demo'
import Util from './util'
jest.mock('./util')
//jest.mock 发现uitl是一个类,会自动把类的构造函数和方法变成jest.fn()
/*
const Util= jest.fn()
until.prototype.a = jest.fn()
until.prototype.b = jest.fn()
如果不满意默认处理,可以自定义在文件__mock__下util.js自行模拟
如果不是很复杂可以直接传递第二个参数,就会执行第二个参数的代码
jest.mock('./util',()=>{
const Util= jest.fn()
Util.prototype..a = jest.fn()
Util.prototype..b = jest.fn()
return Util
})
*/
// 这里测试的关注点是是否有执行,如果类里a b 方法很复杂就会很耗性能,而他们执行的结果并非所关心的,所以用mock模拟
test('测试 demoFunction',()=>{
demoFunction()
expect(Util).toHaveBeenCalled() //是否执行过
expect(Util.mock.instances[0].a).toHaveBeenCalled()
expect(Util.mock.instances[0].b).toHaveBeenCalled()
})
这里引入2种概念 单元测试,和集成测试
单元测试
只关注,该单元的代码,对于外部的引入不关心,如果对性能有影响就会用mock
就想上面的测试demoFunction,我只关心有没有执行过a,b方法并不关心执行的结果
简单说就是对单一功能的测试
集成测试
对单元中所有都测试
我不仅要执行,同时也关心执行后对该单元的影响
对多种功能的集合测试
Test Driven Development (TDD) 测试驱动开发
开发流程
TDD的优势
BDD(Behavior Driven Developmen)
先编写代码,基于用户的行为去编写测试代码
TDD 与BDD 区别
TDD:
BDD:
完成上述基础的学习,那么看懂下面的代码就是轻而易举了
接下来我们将在vue中使用jest
创建一个vue项目,勾选上jest,生成一个包含jest的vue文件
更多请查看 Vue Test Utils 文档
常用api
mount
创建一个包含被挂载和渲染的 Vue 组件的 Wrapper。
shallowMount
和 mount 一样,创建一个包含被挂载和渲染的 Vue 组件的 Wrapper,不同的是被存根的子组件
wrapper options
wrapper.vm
这是该 Vue 实例。你可以通过 wrapper.vm 访问一个实例所有的方法和属性。这只存在于 Vue 组件包裹器 或绑定了 Vue 组件包裹器的 HTMLElement 中
.contains
判断 Wrapper 是否包含了一个匹配选择器的元素或组件。
.emitted
执行一个自定义事件。返回一个包含由 Wrapper vm 触发的自定义事件的对象。
.trigger
为 WrapperArray 的每个 Wrapper DOM 节点都触发一个事件。
.find
返回匹配选择器的第一个 DOM 节点或 Vue 组件的 Wrapper。
.findAll
返回所有,类似jquery
.props
返回 Wrapper vm 的 props 对象。如果提供了 key,则返回这个 key 对应的值
配置 jest.config.js
module.exports = {
preset: '@vue/cli-plugin-unit-jest',
moduleFileExtensions: [ 'js', 'jsx', 'json', 'vue' ], //查找文件的后缀
transform: { //匹配到对应的后缀文件使用对应的转化
'^.+\\.vue$': 'vue-jest', //解析vue语法,
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',//将静态资源转换为字符
'^.+\\.jsx?$': 'babel-jest' //es6语法转换es5
},
transformIgnorePatterns: [ //不需要转换的文件
'/node_modules/'
],
moduleNameMapper: { //路径映射,
'^@/(.*)$': '/src/$1'
},
snapshotSerializers: [ //对快照vue语法编译
'jest-serializer-vue'
],
testMatch: [ //测试文件位置,满足下列规则就当成测试文件
'**/tests/unit/**/*.(spec|test).(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
],
testURL: 'http://localhost/', //测试下的浏览器地址
watchPlugins: [ //添加交互选择
'jest-watch-typeahead/filename',
'jest-watch-typeahead/testname'
]
}
如果你是手动按照最开始那种安装jest, 那么 jest-watch-typeahead/filename、 jest-watch-typeahead/testname、jest-serializer-vue、babel-jest、jest-transform-stub、@vue/cli-plugin-unit-jest、vue-jest 这些包就需要你手动安装一下,
packge.json
如果你的项目没有git初始化,请使用–watchAll
接下来我们找到vue的tests 示例 发现和jest不一样,不慌,我们先用另一个写法实现一下,vue的test-utils, 为我们提供了一些用于测试的api可以查看官网,
import { shallowMount } from '@vue/test-utils'
// shallowMount浅渲染,只渲染当前组件,不渲染包含的子组件,适合单元测试,
// mount 全渲染,适合集成测试
import HelloWorld from '@/components/HelloWorld.vue'
describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message'
//一个 Wrapper 是一个包括了一个挂载组件或 vnode,以及测试该组件或 vnode 的方法。
//wrapper.vm,可以访问到vue的实例
const wrapper = shallowMount(HelloWorld, {
propsData: { msg }
})
//返回 Wrapper 的文本内容。渲染的文本中包含msg
expect(wrapper.text()).toMatch(msg)
})
})
//模拟一个上述实现
import Vue from 'vue'
import HelloWorld from ' @/components/HelloWorld '
describe( 'HelloWorld.vue', () => {
it(' renders props.msg when passed',() =>{
const root = document. createElement( 'div');
root. className ='root' ;
document.body.appendChild( root) ;
new Vue({
render: h => h(HelloWorld,{
props:{
msg: 'dell lee'
}
})
}).$mount( '.root' )
consloe.log(document.body.innerHTML);
expect ( document.getElementsByClassName( 'hello' ). length). toBe(1);
})
})
对store进行的单元测试
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state:{
value:1
},
mutations:{
ADD:(state,data)=>{
state.value = data
}
},
actions:{
commitAdd:({commit},data)=>{
commit("ADD")
}
}
})
export default store
import store from '@/store'
it(`当 store 执行add发生变化`, ()=>{
const value = 123 ;
store.commit( ' ADD', value);
expect( store.state.value).toBe( value);
})
当你的测试文件使用了store,需要在挂载的时候,将store传入,否则store会找不到
import store from '../../store'
it(`
使用store
`,()=>{
const wrapper = mount(todoList,{store})
........
})
我们不需要请求真实的的地址,我们只需要在tests文件同级建立一个新文件夹_mocks_
下面新建一个axios.js
axios.js 示例
export defult get(url){ //模拟get方法
if (url === '/axios.json'){
return new promise((resolve)=>{
const data = [{value:1},{value:2}]
resolve(data)
})
}
}
在test文件调用mount时会执行,mounted,在执行请求获取数据时,test会优先查看_mocks_
下面的axios.js 去替换真实的请求
import store from '../../store'
it(`
1. 用户进入页面时,请求远程数据
2. 列表应该展示远程返回的数据
`,(done)=>{
const wrapper = mount(todoList,{store})
//异步测试,需要使用nextTick和done来等待挂载成功后再执行,jest不会自己等待异步
wrapper.vm.$nextTick(()=>{
const listItems = wrapper.findAll('[data-test=item]')
expect(listItems.length).toBe(2)
done()
})
})
如果存在定时器
模拟的测试组件
- {{item}}
错误示例
在测试代码存在异步的代码,jest并不会去等待定时器执行完,会直接忽略,如果需要,需要使用done()
这样确实可以,但是会等待4秒并不是我们想要的结果
it(`
1. 用户进入页面时,等待4秒
2. 列表应该展示远程返回的数据
`,(done)=>{
const wrapper = mount(todoList)
setTimeout(()=>{
const listItems = wrapper.findAll('[data-test=item]')
expect(listItems.length).toBe(2)
done()
},4000)
})
正确示例
beforEach(()=>{
jest.useFakeTimets() //用于模拟定时器
})
it(`
1. 用户进入页面时,等待4秒
2. 列表应该展示远程返回的数据
`,(done)=>{
const wrapper = mount(todoList)
jest.tunAllTimers() //如果遇到timers让他立即执行
wrapper.vm.$nextTick(()=>{
const listItems = wrapper.findAll('[data-test=item]')
expect(listItems.length).toBe(2)
done()
})
})
下一篇文章
前端自动化测试(二)TDD与BDD 实战todoList