概念
jest是Facebook出品的一个JavaScript开源测试框架。内置了零配置、自带断言、测试覆盖率工具等,实现了开箱即用。
jest的主要特点
- 零配置
- 自带断言
- 快照测试功能,可以对常见前端框架进行自动化测试
- jest测试用例是并行执行的,而且只执行发生改变的文件所对应的测试,提升了速度
- 测试覆盖率
- Mock模拟
安装使用
npm i -D jest
npm i -D @types/jest
配置文件
初始化jest默认文件
$ npx jest --init
npx: 332 安装成功,用时 29.723 秒
The following questions will help Jest to create a suitable configuration for your project
√ Would you like to use Jest when running "test" script in "package.json"? ... yes
√ Would you like to use Typescript for the configuration file? ... no
√ Choose the test environment that will be used for testing ? jsdom (browser-like)
√ Do you want Jest to add coverage reports? ... yes
√ Which provider should be used to instrument code for coverage? ? babel
√ Automatically clear mock calls, instances and results before every test? ... yes
生成jest.config.js配置文件,并有jest的所有注释的配置文件
module.exports = {
clearMocks: true,
collectCoverage: true,
coverageDirectory: "coverage",
testEnvironment: "jsdom",
};
监视模式运行
监视文件的更改并在任何更改时重新运行所有测试
jest --watchAll
需要git支持
jest --watch
使用ES6模块
安装解析依赖
npm i -D babel-jest @babel/core @babel/preset-env
配置babel.config.js
module.exports = {
presets: [['@babel/preset-env',{
targets: {
node: 'current'
}
}]]
}
jest全局api
Test函数
test函数的别名:it(name, fn, timeout)
Expect匹配器
it('匹配器', () => {
expect(2 + 2).tobe(4);
expect({name: 'alvin'}).toEqual({name: 'alvin'});
expect('Christoph').toMatch(/stop/);
expect(4).toBeGreaterThan(3);
expect(3).toBeLessThan(4);
})
describe函数
describe创建一个将几个相关测试组合在一起的块。
生命周期钩子
afterALl(fn, timeout)
afterEach(fn, timeout)
beforeAll(fn, timeout)
beforeEach(fn, timeout)
jest对象
Jest对象自动位于每个测试文件中的范围内。Jest对象中的方法有助于创建模拟,并让你控制Jest的整体行为。也可以通过import {jest} form '@jest/globals’导入。详细参考: https://jestjs.io/docs/jest-objest
jest对象中有许多的功能函数,例如:模拟定时器:jest.useFakeTimers()
常用匹配器
运行单个的测试文件
npm run test -- expect.spec.js
官方使用文档:https://jestjs.io/docs/using-matchers
Truthiness
test('null', () => {
const n = null;
expect(n).toBeNull();
expect(n).toBeDefined();
expect(n).not.toBeUndefined();
expect(n).not.toBeTruthy();
expect(n).toBeFalsy();
});
test('zero', () => {
const z = 0;
expect(z).not.toBeNull();
expect(z).toBeDefined();
expect(z).not.toBeUndefined();
expect(z).not.toBeTruthy();
expect(z).toBeFalsy();
});
Numbers
test('two plus two', () => {
const value = 2 + 2;
expect(value).toBeGreaterThan(3);
expect(value).toBeGreaterThanOrEqual(3.5);
expect(value).toBeLessThan(5);
expect(value).toBeLessThanOrEqual(4.5);
expect(value).toBe(4);
expect(value).toEqual(4);
});
test('adding floating point numbers', () => {
const value = 0.1 + 0.2;
expect(value).toBeCloseTo(0.3);
});
Strings
test('there is no I in team', () => {
expect('team').not.toMatch(/I/);
});
test('but there is a "stop" in Christoph', () => {
expect('Christoph').toMatch(/stop/);
});
Arrays and iterables
const shoppingList = [
'diapers',
'kleenex',
'trash bags',
'paper towels',
'milk',
];
test('the shopping list has milk on it', () => {
expect(shoppingList).toContain('milk');
expect(new Set(shoppingList)).toContain('milk');
});
Exceptions
function compileAndroidCode() {
throw new Error('you are using the wrong JDK');
}
test('compiling android goes as expected', () => {
expect(() => compileAndroidCode()).toThrow();
expect(() => compileAndroidCode()).toThrow(Error);
expect(() => compileAndroidCode()).toThrow('you are using the wrong JDK');
expect(() => compileAndroidCode()).toThrow(/JDK/);
});
测试异步代码
回调函数的方式使用
function getData(callback){
setTimeout(() => {
callback({foo: 'bar'})
}, 2000)
}
it("异步测试", (done) => {
getData(data =>{
done()
expect(data).toEqual({foo: 'bar'})
})
})
promise方式回调
function getData(data){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({foo: 'bar'})
}, 2000)
})
}
it("异步promise测试", (done) => {
getData().then(data => {
done()
expect(data).toEqual({foo: 'bar'})
})
})
it("异步promise直接return", () => {
return getData().then(data => {
expect(data).toEqual({foo: 'bar'})
})
})
it("异步promise 使用.resolve、.rejects", () => {
return expect(getData()).resolves.toEqual({foo: 'bar'})
})
async和await方式
function getData(){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({foo: 'bar'})
}, 2000)
})
}
it('async和await方式测试异步代码', async () => {
const data = await getData();
expect(data).toEqual({foo: 'bar'})
})
it('async和await方式测试异步代码', async () => {
try{
await getData();
}catch(err) {
expect(err).toMatch('hello')
}
})
test('the data is peanut butter', async () => {
await expect(fetchData()).resolves.toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
await expect(fetchData()).rejects.toMatch('error');
});
mock定时器
function getData(){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({foo: 'bar'})
}, 2000)
})
}
jest.useFakeTimers()
it('timer mock', () => {
expect.assertions(1);
getData().then(data => {
expect(data).toEqual({foo: 'bar'})
})
jest.runAllTimers()
})
mock functions
function forEach(items, callback){
for(let index = 0; index < items.length; index++){
callback(items[index], index)
}
}
it('Mock Function', () => {
const items = [1, 2, 3];
const mockFn = jest.fn((value, index) => {
return value + 1;
})
forEach(items, mockFn)
expect(mockFn.mock.calls.length).toBe(items.length)
expect(mockFn.mock.calls[0][0]).toBe(1)
expect(mockFn.mock.calls[0][1]).toBe(0)
})
user.js
import axios from 'axios';
export const getAllUsers = () => {
return axios.get('/user.json').then(resp => resp.data)
}
mock-function
import { getAllUsers } from "./user";
import axios from "axios";
jest.mock('axios');
it('fetch Users', async () => {
const users = [{name: 'bob'}]
const resp = {data: users}
axios.get.mockResolvedValue(resp)
const data = await getAllUsers()
expect(data).toEqual(users)
})
mock 函数实现
./foo.js
export default function(){
console.log('foo')
}
import foo from './foo'
jest.mock('./foo')
foo.mockImplementations(() => {
return 123;
})
it('mock Implementations', () => {
expect(foo()).toBe(123)
})
钩子函数
beforEach(() => {
console.log('beforeEach')
})
afterEach(() => {
console.log('afterEach')
})
beforAll(() => {
console.log('beforAll')
})
afterAll(() => {
console.log('afterAll')
})
DOM测试
function renderHtml(){
const div = document.createElement('div');
div.innerHTML = `<h1>Hello World</h1>`
document.body.appendChild(div)
}
it('Dom Test', () => {
renderHtml()
expect(document.querySelector('h1').innerHTML).toBe('Hello World')
})
Vue组件测试
import Vue from 'vue/dist/vue';
function renderVueComponent(){
document.body.innerHTML = `<div id="app"></div>`
new Vue({
template: `<div id="app"><h1>{{message}}</h1></div>
`,
data(){
return {
message: 'Hello World'
}
}
}).$mount('#app')
}
it('测试Vue组件', () => {
renderVueComponent()
console.log(document.body.innerHTML)
expect(document.body.innerHTML).toMatch(/Hello World/);
})
快照测试
it('快照测试', () => {
renderVueComponent()
expect(document.body.innerHTML).toMatchSnapshot()
})
更新快照的命令
npx jest --updateSnapshot
|