npm install -D @babel/core @babel/preset-env
复制代码
.babelrcjavascript
{
"presets": [
[
"@babel/preset-env", {
"targets": {
"node": "current"
}
}
]
]
}
复制代码
jest 在运行前会检查是否安装 babel,若是安装了会去取 .babelrc 文件,结合 babel 将代码进行转化,运行转化后的代码。 3. jest 默认配置css
npx jest --init
复制代码
git
结合使用,会比较现有文件和 commit 的文件的差别,只测试差别文件const throwError = () => {
throw new Error('error')
}
it('can throw error', () => {
expect(throwError).toThrow('error') // 判断throw函数能够抛出异常,异常信息为 "error"。也能够写正则
})
复制代码
这里有个小技巧:当咱们想忽略掉单个文件中的其余测试用例,只针对一个测试用例作调试的时候,能够加上 .only
html
it.only('test', () => {
// ...
})
复制代码
但这并不会忽略其余测试文件的测试用例前端
这里有三个异步方法,对这三个方法进行代码测试,"www.dell-lee.com/react/api/d…" 会返回 {success: true}, "www.dell-lee.com/react/api/4…" 则不存在。vue
import axios from 'axios'
export function getData1() {
return axios.get('http://www.dell-lee.com/react/api/demo.json')
}
export function getData2(fn) {
axios.get('http://www.dell-lee.com/react/api/demo.json').then(res => {
fn(res)
})
}
export function get404() {
return axios.get('http://www.dell-lee.com/react/api/404.json')
}
复制代码
对于异步代码测试,时机很重要,必须保证咱们的测试用例在异步代码走完以后才结束。有如下几种办法:java
import {getData1, getData2, get404} from './fetchData/fetchData'
it('getData1 方法1', (done) => {
getData1().then(res => {
expect(res.data).toEqual({
success: true
})
done() // 若是不加 done,还没执行到 .then 方法,测试用例已经结束了
})
})
it('getData1 方法2', () => {
return getData1().then(res => {
expect(res.data).toEqual({
success: true
})
})
})
it('getData2 方法2', (done) => {
getData2((res) => {
expect(res.data).toEqual({
success: true
})
done()
})
})
it('getData1 方法3', async () => {
const res = await getData1()
expect(res.data).toEqual({
success: true
})
})
/*********** 重点关注 ***********/
it('get404', (done) => {
expect.assertions(1)
get404().catch(r => {
expect(r.toString()).toMatch('404')
done()
})
})
复制代码
重点讲一下上面的最后一个测试用例,假设咱们如今有一个返回的是 404 的接口,咱们须要对这个接口测试,指望他返回 404。 咱们用 catch 捕获,在 catch 中判断。node
可是,假如这个接口返回的不是 404,而是正常返回 200,这个 catch 则不会执行,expect 也不会执行,测试依然是经过的。这不符合咱们的预期!因此,咱们须要加上 expect.assertions(1)
进行断言:下面必定会执行一个 expectreact
固然,也能够用 async await 方法进行 404 接口的测试ios
it('get404 方法3', async () => {
await expect(get404()).rejects.toThrow()
})
复制代码
前四个钩子使用起来很简单,调用方法以下:git
beforeAll(() => {
// ...
})
复制代码
若是测试先后要作一些处理,尽量写在这些钩子函数中,他能保证必定的执行顺序。
describe 能够用来进行用例分组,为了让咱们的测试输出结果更好看,更有层次。 同时,在每一个 describe 中都有上面 4 个钩子函数的存在,咱们来看看具体的状况:
describe('测试 Button 组件', () => {
beforeAll(...) // 1
beforeEach(...) // 2
afterEach(...) // 3
afterAll(...) // 4
describe('测试 Button 组件的事件', () => {
beforeAll(...) // 5
beforeEach(...) // 6
afterEach(...) // 7
afterAll(...) // 8
it('event1', ()=>{...})
})
})
复制代码
上面钩子函数的执行顺序是: 1 > 5 > 2 > 6 > 3 > 7 > 4 > 8
外部的钩子函数对 describe 内部的用例也生效,执行顺序为:先外部后内部
前面提到了能够测试异步代码,对于一些接口都能进行请求测试。但假如每个接口都真的发起请求,那一次测试须要耗费的时间是不少的。 这时候咱们能够模拟请求方法,步骤以下:
import axios from 'axios'
export function getData() {
return axios.get('http://www.dell-lee.com/react/api/demo.json')
}
复制代码
export function getData() {
return Promise.resolve({
success: true
})
}
复制代码
jest.mock('./mock/mock.js') // 声明下面引入的 getData 方法是 jest 模拟的,若是不须要引入该方法则不须要声明
import {getData} from './mock/mock.js' // 导入 mock.js,但实际上 jest 会导入 __mocks__ 下的 mock.js
test('mock 方法测试', () => {
getData().then(data => {
expect(data).toEqual({
success: true
})
done()
})
})
复制代码
除了上面的这种办法,还能在 jest.config.js 中配置自动开启 mock,这样 jest 会自动去查找当前文件同级有没有 mock 文件夹,里面有没有对应文件
module.exports = {
automock: true
}
复制代码
讲了两种 mock 的方法,还有一种极端状况须要避免 mock:
咱们在 mock.js 中定义了一个须要 mock 的 getData 方法,又另外定义了一个不须要 mock 的普通方法,当咱们在测试文件导入的时候,须要避免 jest 去 mocks/mock.js 下找这个普通方法,这里须要用 jest 提供的方法导入:
const { regularMethod } = jest.requireActual('./mock/mock.js')
复制代码
当咱们有以下代码须要测试的时候:
export default (fn) => {
setTimeout(() => {
fn()
}, 3000)
}
复制代码
咱们不可能老是去等待定时器,这时候咱们要用 Jest 来操做时间!步骤以下:
jest.useFakeTimers()
使用 jest “自制的” 定时器,这里放在 beforeEach 里面是由于快进时间可能被调用屡次,我但愿在每一个测试用例里,这个时钟都是初始状态,不会互相影响。jest.advanceTimersByTime(3000)
,这个方法能够调用任意次,快进的时间会叠加。特别说明一下:jest.fn() 生成的是一个函数,这个函数能被监听调用过几回。
import timer from './timer/timer'
beforeEach(() => {
jest.useFakeTimers()
})
it('timer 测试', () => {
const fn = jest.fn()
timer(fn)
jest.advanceTimersByTime(3000)
expect(fn).toHaveBeenCalledTimes(1)
})
复制代码
一样的,当咱们只关注类的方法是否被调用,而不关心方法调用产生的结果时,能够 mock 类
在 util/util.js 中定义了 Util 类
export class Util {
a() {}
b() {}
}
复制代码
在 util/useUtil 中调用了这个类
import {Util} from './util'
export function useUtil() {
let u = new Util()
u.a()
u.b()
}
复制代码
咱们须要测试 u.a 和 u.b 被调用,jest.mock('./util/util')
会将 Util、Util.a、Util.b 都 mock 成 jest.fn
测试用例以下:
jest.mock('./util/util') // mock Util 类
import {Util} from './util/util'
import {useUtil} from './util/uesUtil'
test('util 的实例方法被执行了', () => {
useUtil()
expect(Util).toHaveBeenCalled()
expect(Util.mock.instances[0].a).toHaveBeenCalled()
expect(Util.mock.instances[0].b).toHaveBeenCalled()
})
复制代码
Vue 提供了 @vue/test-utils 来帮助咱们进行单元测试,建立 Vue 项目的时候勾选测试选项会自动帮咱们安装。
先来介绍两个经常使用的挂载方法:
再来看一个简单的测试用例:
import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'
describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = shallowMount(HelloWorld, {
propsData: { msg }
})
expect(wrapper.props('msg')).toBe(msg)
})
})
复制代码
shallowMount 会返回一个 wrapper,这个 wrapper 上面会包含不少帮助咱们测试的方法,详见
快照测试的意思是,会将组件像拍照同样拍下来,存底。下次运行测试用例的时候,若是组件发生变化,和快照不同了,就会报错。
测试用例写法以下: 第一次测试会保存 wrapper 的快照,第二次会比较当前 wrapper 和快照的区别
describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = shallowMount(HelloWorld, {
propsData: { msg }
})
expect(wrapper).toMatchSnapshot()
})
})
复制代码
咱们再来看看快照长什么样子:
当快照发生变化时,咱们能够在终端按 u
进行更新快照
覆盖率测试是对测试彻底程度的一个评估,测试覆盖到的业务代码越多,覆盖率越高。
在 jest.config.js 中咱们能够设置 collectCoverageFrom
,来设置须要进行覆盖率测试的文件,这里咱们测试一下全部的 .vue
文件,忽略 node_modules
下全部文件。
要注意,在 Vue 中配置 jest,参考文档
而后添加一条 script 命令,就能进行测试了:
"test:unit": "vue-cli-service test:unit --coverage"
复制代码
执行命令会生成 coverage
文件夹,Icov-report/index.html
里会可视化展现咱们的测试覆盖率
若是咱们在组件中引入了 Vuex 状态或者使用了相关方法,在测试用例里,挂载组件的时候只须要传入 vuex 的 store 便可。
import store from '@/store/index'
const wrapper = mount(HelloWorld, {
store
})
复制代码
就拿 shallowMount
来讲,这个 api 就很适合单元测试,单元测试不关注单元之间的联系,对每一个单元进行独立测试, 这也使得它代码量大,测试间过于独立。在进行一些函数库的测试,各个函数比较独立的时候,就很适合单元测试。
在进行一些业务组件测试时,须要关注组件间的联系,比较适合用集成测试。
TDD:测试驱动开发,先写测试用例,而后根据用例写代码,比较关注代码自己。以下:
describe('input 输入回车,向外触发事件,data 中的 inputValue 被赋值', () => {
const wrapper = shallowMount(TodoList)
const inputEle = wrapper.find('input').at(0)
const inputContent = '用户输入内容'
inputEle.setValue(inputContent)
// expect:add 事件被 emit
except(wrapper.emitted().add).toBeTruthy()
// expect:data 中的 inputValue 被赋值为 inputContent
except(wrapper.vm.inputValue).toBe(inputContent)
})
复制代码
TDD 关注代码内部如何实现,关注事件是否触发?属性是否设置?data 数据是否被更新?
BDD:用户行为驱动开发,先写完业务代码,而后站在用户的角度去测试功能,不关注代码实现过程,只是经过模拟用户操做测试功能。
好比下面这个用例:
describe('TodoList 测试', () => {
it(` 1. 用户在 header 输入框输入内容 2. 键盘回车 3. 列表项增长一项,内容为用户输入内容 `, () => {
// 挂载 TodoList 组件
const wrapper = mount(TodoList)
// 模拟用户输入
const inputEle = wrapper.find('input').at(0)
const inputContent = '用户输入内容'
inputEle.setValue(inputContent)
// 模拟触发的事件
inputEle.trigger('content')
inputEle.trigger('keyup.enter')
// expect:列表项增长对应内容
const listItems = wrapper.find('.list-item')
expect(listItems.length).toBe(1) // 增长 1 项
expect(listItems.at(0).text()).toContain(inputContent) // 增长 1 项
})
})
复制代码
参考: