1.安装
$ npm i jest (全局安装)
$ npm init -y (创建package.json)
$ npm install -D babel-jest babel-core babel-preset-env regenerator-runtime (让node项目支持import)
2.使用
- jest 指定文件夹 (会运行下面所有xx.test.js 的文件)
- 创建项目文件夹下的__test__和对应下面的.test.js只是用于规范命名,不是强制需要,除非设置如下:
//通过设置 package.json 的 可以指定约束测试文件夹
"jest":
{
"testMatch": [
"<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
"<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}"
],
}
2.1 或者直接创建jest.config.js
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['./tests'],
testRegex: '.*\\.test\\.ts$',
moduleFileExtensions: ['ts', 'js', 'json', 'node'],
}
- 执行命令
npm run jest
"scripts": {
"jest": "jest"
},
3.变量期望
expect(XXX).toBe(YYY)
// functions.js
export default {
sum(a, b) {
return a + b;
}
}
// functions.test.js
import functions from './sum.js';
test('sum(2 + 2) 等于 4', () => {
expect(functions.sum(2, 2)).toBe(4);
})
4.方法期望
1.jest.fn() 普通方法断言
test('测试jest.fn()返回固定值', () => {
let mockFn = jest.fn().mockReturnValue('default');
// 断言mockFn执行后返回值为default
let a = mockFn();
expect(mockFn).toBeCalledTimes(1);
expect(a).toBe('default');
})
test('测试jest.fn()内部实现', () => {
let testFn = (num1,num2) => {
return num1 * num2;
}
let mockFn = jest.fn( testFn );
// 断言mockFn执行后返回100
expect(mockFn(20, 10)).toBe(200);
})
2.通过done参数延迟测试的时间
处理settimeout定时器的用例,默认定时回调任务是不会只想,当作测试成功处理。
test('测试xxx', done => {
func()//测试带有延迟的方法
// 延时2s结束
setTimeout(done, 2000)
})
3.expect.assertions 实现异步断言
// functions.js 记得先安装 npm i axios
import axios from 'axios';
export default {
fetchUser() {
return axios.get('http://jsonplaceholder.typicode.com/users/1')
.then(res => res.data)
.catch(error => console.log(error));
}
}
import functions from './functions';
test('开始异步断言', () => {
expect.assertions(1);
return functions.fetchUser()
.then(data => {
expect(data.name).toBe('Leanne Graham');
});
})
4.测试调用次数与参数
//测试订阅发布模式
test('开始异步断言', () => {
const mockFn = jest.fn()
const Connention = require('../index')//这里为订阅发布的代码
const conn = new Conection();
//订阅两次
conn.onConn(mockFn)
conn.onConn(mockFn)
setTimeout(() => {
conn.contention("连接1完成")
})
setTimeout(() => {
conn.contention("连接2完成")
})
setTimeout(() => {
const calls = mockFn.mock.calls
expect(calls.length).toBe(4)
expect(calls.[0][0]).toBe('连接1完成')
expect(calls.[1][0]).toBe('连接1完成') //第一个[1] 为调用的第一个参数,第二个[0]为第二个参数
expect(calls.[2][0]).toBe('连接2完成')
expect(calls.[3][0]).toBe('连接2完成')
})
})
jest每次限制5秒的时间
test('开始异步断言', (done) => {
let testFn = (num1,num2) => {
return num1 * num2;
}
let mockFn = jest.fn( testFn );
// 断言mockFn执行后返回100
const mockFn = jest.fn()
setTimeout(() => {
expect(mockFn(20, 10)).toBe(200);
},6000) //会提示超时
})
这是因为Jest每个测试用例默认只给了5s。
jest.useFakeTimers()//使用jest 自己的模拟的定时器
jest.advanceTimersByTime(6000); //把时间往后拨6秒
jest.runAllTimers() //将全部定时器立即运行结束
jest.runOnlyPendingTimers() //当有多个定时器 如果内部有多个定时器,只想运行一个定时器
jest.useRealTimers()//切换为js正常时间
//使用setImmediate 实现微队列执行
const flushPromises = () => new Promise(resolve => setImmediate(resolve));
it('测试一段时间之后自动关闭', async () => {
jest.useFakeTimers() //使用jest的定时器
setTimeout(() => {
//....
},6000)
jest.runOnlyPendingTimers()//跳过当前第一个settimeout
await flushPromises()
expect(xxxx).toBe(false)
})
完整的用法
const flushPromises = () => new Promise(resolve => setImmediate(resolve));
it('测试一段时间之后自动关闭', async () => {
jest.useFakeTimers() //使用jest的定时器
const wrapper = mount(Notification, {
props: {
duration: 8000
}
})
jest.runOnlyPendingTimers()//跳过当前第一个settimeout
await flushPromises()
expect(wrapper.get('.el-notification').isVisible()).toBe(false)
})
vue 自动化测试
vue add @vue/unit-jest
// counter.js
export default {
template: `
<div>
<span class="count">{{ count }}</span>
<button @click="increment">Increment</button>
</div>
`,
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
}
}
挂载组件
// test.js
// 从测试实用工具集中导入 `mount()` 方法
// 同时导入你要测试的组件
import { mount } from '@vue/test-utils'
import Counter from './counter'
// 现在挂载组件,你便得到了这个包裹器
const wrapper = mount(Counter)
// 你可以通过 `wrapper.vm` 访问实际的 Vue 实例
const vm = wrapper.vm
// 在控制台将其记录下来即可深度审阅包裹器
// 我们对 Vue Test Utils 的探索也由此开始
console.log(wrapper)
测试组件渲染出来的 HTML
import { mount } from '@vue/test-utils'
import Counter from './counter'
describe('Counter', () => {
// 现在挂载组件,你便得到了这个包裹器
const wrapper = mount(Counter)
it('renders the correct markup', () => {
expect(wrapper.html()).toContain('<span class="count">0</span>')
})
// 也便于检查已存在的元素
it('has a button', () => {
expect(wrapper.contains('button')).toBe(true)
})
})
模拟用户交互
it('button click should increment the count', () => {
expect(wrapper.vm.count).toBe(0)
const button = wrapper.find('button')
button.trigger('click')
expect(wrapper.vm.count).toBe(1)
})
例子-通知组件
Notification.vue
<template>
<div v-show="visible" :class="['el-notification',verticalProperty,horizontalClass]" :style="positionStyle"
@click="onClickHandler">
<div class="el-notification__title">
{{ title }}
</div>
<div class="el-notification__content">
{{ message }}
</div>
<button v-if="showClose" class="el-notification__close-button" @click="onCloseHandler"></button>
</div>
</template>
<script setup lang="ts">
import { getCurrentInstance, ref, computed, withDefaults } from 'vue'
const instance = getCurrentInstance()
const visible = ref(true)
interface Props {
title: string,
message: string,
verticalOffset: number,
type: string,
position: string,
showClose: boolean,
duration:number
}
const props = withDefaults(defineProps<Props>(), {
title: '',
message: '',
verticalOffset: 0,
type: '',
position: 'top-right',
showClose: true,
duration:3
})
const verticalOffsetVal = ref(props.verticalOffset)
const typeClass = computed(() => {
return props.type ? `el-icon-${props.type}` : ''
})
const horizontalClass = computed(() => {
return props.position.endsWith('right') ? 'right' : 'left'
})
const verticalProperty = computed(() => {
return props.position.startsWith('top') ? 'top' : 'bottom'
})
const positionStyle = computed(() => {
return {
[verticalProperty.value]: `${verticalOffsetVal.value}px`
}
})
function onCloseHandler() {
visible.value = false
}
let timer
function delayClose() {
if (props.duration > 0) {
timer = setTimeout(() => {
onCloseHandler()
}, props.duration)
}
}
delayClose()
</script>
<style lang="scss">
.el-notification {
position: fixed;
right: 10px;
top: 50px;
width: 330px;
padding: 14px 26px 14px 13px;
border-radius: 8px;
border: 1px solid #ebeef5;
background-color: #fff;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
overflow: hidden;
}
</style>
Notification.spec.ts
import Notification from './Notification.vue';
import { mount } from '@vue/test-utils';
describe("Notification", () => {
it('测试标题title', () => {
const title = 'this is a title'
const wrapper = mount(Notification, {
props: {
title
}
})
expect(wrapper.get('.el-notification__title').text()).toContain(title)
})
it('测试内容', () => {
const message = 'this is a message'
const wrapper = mount(Notification, {
props: {
message
}
})
expect(wrapper.get('.el-notification__content').text()).toContain(message)
})
it('测试渲染位置', () => {
const position = 'bottom-right'
const wrapper = mount(Notification, {
props: {
position
}
})
expect(wrapper.find('.el-notification').classes()).toContain('right')
expect(wrapper.vm.verticalProperty).toBe('bottom')
const el = <HTMLElement>wrapper.find('.el-notification').element
expect(el.style.bottom).toBe('0px')
})
it('测试位置偏移', () => {
const verticalOffset = 50
const wrapper = mount(Notification, {
props: {
verticalOffset
}
})
expect(wrapper.vm.verticalProperty).toBe('top')
const el = <HTMLElement>wrapper.find('.el-notification').element
expect(el.style.top).toBe( `${verticalOffset}px` )
})
it('测试按钮参数', () => {
const showClose = false
const wrapper = mount(Notification, {
props: {
showClose
}
})
expect(wrapper.find('.el-notification__close-button').exists()).toBe(showClose)
// expect(wrapper.find('.el-icon-close').exists()).toBe(true)
})
it('测试点击关闭按钮', async () => {
const showClose = true
const wrapper = mount(Notification, {
props: {
showClose
}
})
const closeBtn = wrapper.get('.el-notification__close-button')
await closeBtn.trigger('click')
expect(wrapper.get('.el-notification').isVisible()).toBe(false)
})
it('测试一段时间之后自动关闭', async () => {
jest.useFakeTimers()
const wrapper = mount(Notification, {
props: {
duration: 8000
}
})
const flushPromises = () => new Promise(resolve => setImmediate(resolve));
jest.runOnlyPendingTimers()
await flushPromises()
expect(wrapper.get('.el-notification').isVisible()).toBe(false)
})
})