jest基础
匹配器
jest默认环境是node
如果想在jest环境使用esmodule,需要借助@babel/core转化工具, @babel/preset-env指明如何转化
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "current"
}
}
]
]
}
只需要一个项目中安装jest,然后执行 npx jest --watch,jest就会自动将所有的.test.js结尾的文件中的测试用例执行。
jest提供了很多匹配器,最基本的使用
test('测试加法', ()=>{
expect(10).toBe(10)
})
test('测试内容', ()=>{
expect({a: 1}).toEqual({a: 1})
})
如toBe, toEqual,等等还有很多。当执行npx jest --watch的时候,jest就会执行这些测试用例并返回结果。
PASS ./9.test.js
√ 测试加法 (2ms)
√ 测试内容 (1ms)
Snapshot Summary
? 1 snapshot file obsolete from 1 test suite. To remove it, press `u`.
? ? __snapshots__学习\demo.test.js.snap
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 1 file obsolete, 0 total
Time: 0.987s
Ran all test suites related to changed files.
测试通过。
还有更多的适配器如:
expect(10).toBeGreaterThan(9)
expect( 0.1 + 0.2).toBeCloseTo(0.3)
expect('abcd').toMatch(/ab/)
expect([1,2]).toContaine(1)
expect(()=>{throw new Error('123')}).toThrow()
expect(()=>{throw new Error('123')}).toThrow('123')
更多适配器可以查看官网。
命令行工具
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 1 file obsolete, 0 total
Time: 2.302s, estimated 3s
Ran all test suites related to changed files.
Watch Usage: Press w to show more.
执行完后,下面会提示输入W显示更多指令
Watch Usage
? Press a to run all tests.
? Press f to run only failed tests.
? Press p to filter by a filename regex pattern.
? Press t to filter by a test name regex pattern.
? Press u to update failing snapshots.
? Press q to quit watch mode.
? Press Enter to trigger a test run.
测试异步
const fetchData = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ success: true });
}, 2000);
});
export { fetchData };
因为默认情况下,Jest 测试在执行结束后完成。
test('the data is peanut butter', () => {
fetchData().then(res=>{
expect(res).toEqual({success: true})
})
});
那么其实不管输入什么,该test就会执行正确。
正确测试异步方法:
1 done
test("fetchData 返回结果为{success: true}", (done) => {
fetchData()
.then((res) => {
expect(res).toEqual({ success: true });
done();
})
.catch((err) => {
done(err);
});
});
只有调用done,才会正确测试该test。
2 promise
正常使用promise.then,但是一定要return。而且可以使用expect.assertions(1)表示至少执行一次expect。
因为有些场景,我们只俘获了catch,并无俘获then,那么Promsie成功的时候,这个测试将会没有执行。所以需要这个来确定至少执行一次。否则失败。
test("fetchData 返回结果为{success: true}", () => {
expect.assertions(1)
return fetchData()
.then((res) => {
expect(res).toEqual({ success: true });
})
.catch((err) => {
expect(err).toMatch("error");
});
});
3 resolves/reject
test("fetchData 返回结果为{success: true}", () => {
return expect(fetchData()).resolves.toMatchObject({
success: true,
});
return expect(fetchData()).rejects.toThrow()
});
4 asycn/await
记得错误处理
test("fetchData 返回结果为{success: true}", async () => {
try {
const data = await fetchData();
expect(data).toEqual({
success: true,
});
} catch (err) {
expect(err).toThrow()
}
});
钩子函数
jest在执行test用例的时候,提供了很多钩子,用来初始化一些东西或者做一些销毁工作,如执行前调用,执行后调用等等。如
beforeAll(()=>{
console.log('beforeAll');
})
afterAll(()=>{
console.log('afterAll');
})
他们分别会在所有的钩子执行前执行和执行后执行。
beforeEach(()=>{
console.log('beforeEach');
})
afterEach(()=>{
console.log('afterEach');
})
beforeEach和afterEach分别会在每个test执行之前和之后运行。
分组
相同的意义的测试用例可以写在同一个分组中,相当于ts的namespace,jest提供了describe
describe("测试加法", () => {
beforeAll(() => {
console.log("内部 eforeAll");
});
afterAll(() => {
console.log("内部 afterAll");
});
afterEach(() => {
console.log("内部 afterEach");
});
beforeEach(() => {
console.log("测试加法 beforeEach");
counter = new Counter();
});
test("test Counter addOne", () => {
counter.addOne();
expect(counter.number).toBe(1);
console.log("addOne");
});
});
如,describe内部的钩子,只会在内部执行。而整个test文件,可以看作是外层包装了一个descrbie(‘xx’,()=>{// test文件的内容})
那么desceibe内部的钩子和外部的钩子,执行顺序是怎样呢?
以下面的为例子
beforeAll(() => {
console.log("外部 beforeAll");
});
afterAll(() => {
console.log("afterAll");
});
beforeEach(() => {
console.log("外部 beforeEach");
});
afterEach(() => {
console.log("外部 afterEach");
});
describe("测试加法", () => {
beforeAll(() => {
console.log("内部 eforeAll");
});
afterAll(() => {
console.log("内部 afterAll");
});
afterEach(() => {
console.log("内部 afterEach");
});
beforeEach(() => {
console.log("测试加法 beforeEach");
});
test("test Counter addOne", () => {
console.log("descrbie内部的测试 addOne");
});
});
执行顺序就是
外部 beforeAll
内部 beforeAll
外部 beforeEach
测试加法 beforeEach
descrbie内部的测试 addOne
内部 afterEach
外部 afterEach
内部 afterAll
外部 afterAll
技巧就是:外部beforeAl > 内部beforeAll > 外部beforeEach > 内部beforeEach > test执行 > 内部afterEach > 外部 afterEach > 内部 afterAll > 外部 afterAll
mock
jest提供了mock的功能,可以模拟请求数据的函数,模拟返回的数据。
其中
jest.fn 返回一个mock函数,1 捕获函数的调用和返回结果, 2 可以自由地设置返回结果 3模拟axios请求返回特定数据
test("测试", () => {
const func = jest.fn((a) => {
return a + 4;
});
func.mockReturnValueOnce("11111").mockReturnValueOnce("2222");
func(1);
func(2);
func(3);
func(4);
expect(func.mock.results);
console.log(func.mock);
func执行的时候,实际上是jest.fn()在执行,func.mock提供了很多内部,包括调用多少次,每次的入参,每次的返回等等。并且可以通过func.mockReturnValueOnce(‘xx’)默契一次func调用的时候的返回。
jest.mock可以改变函数的实现
import axios from 'axios'
jest.mock('axios')
test.only('测试 axios',async()=>{
axios.get.mockResolvedValue({data: '123'})
console.log('axios', axios);
await axios.get('xxx').then(data=>{
console.log(data);
expect(data).toEqual({data: '123'})
})
})
jest.mock(‘axios’)可以将axios上的所有属性变成jest.fn。打印出来的axios是
console.log 9.test.js:5
axios <ref *1> [Function: wrap] {
_isMockFunction: true,
getMockImplementation: [Function (anonymous)],
mock: [Getter/Setter],
mockClear: [Function (anonymous)],
mockReset: [Function (anonymous)],
mockRestore: [Function (anonymous)],
mockReturnValueOnce: [Function (anonymous)],
mockResolvedValueOnce: [Function (anonymous)],
mockRejectedValueOnce: [Function (anonymous)],
....
}
axios已经被改造了。可以通过axios.get.mockResolvedValue({data: ‘123’})模式get请求返回的数据,测试如下:
PASS ./9.test.js
√ 测试 axios (10ms)
console.log 9.test.js:6
{ data: '123' }
Snapshot Summary
? 1 snapshot file obsolete from 1 test suite. To remove it, press `u`.
? ? __snapshots__学习\demo.test.js.snap
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 1 file obsolete, 0 total
Time: 2.568s, estimated 3s
Ran all test suites related to changed files.
测试通过,get请求返回的data就是{data: ‘123’}
自己改造函数
jest执行测试用例的时候,并不希望真正的发送请求。所以可以通过jest.mock模拟。
假设fetchData是代码中真正发送请求的函数,我们想验证这个函数的正确性,又不想发送请求。可以在根目录下创建__mock__ 文件夹,命名相同的文件名字,如demo.js然后实现
const fetchData = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ success: true });
}, 2000);
});
export {fetchData }
接着引入fetchData的时候
import {fetchData} from './demo'
jest.mock('./demo')
test('xxx',()=>{
return fetchData().then(res=>.....)
})
其次,如果jest.mock(’./demo’)找不到__mock__ 下的文件的时候,jest内部就会自动模拟,如上面的axios。
mock模拟类
class Util {
a(){
setTimeout(()=>{
console.log(123);
}, 3000)
}
b(){
setTimeout(()=>{
console.log(123);
}, 5000)
}
}
export default Util
import Util from './util'
jest.mock('./util')
我们也可以自己定义
const Util = jest.fn()
Util.prototype.a = jest.fn()
Util.prototype.b = jest.fn()
这样jest.mock(’./util’)就会来这里找。
甚至可以
jest.mock('./util', ()=>{
const Util = jest.fn()
Util.prototype.a = jest.fn()
Util.prototype.b = jest.fn()
return Util
})
作为第二个参数,在里面定义并且返回。这就是jest.mock的用法。
定时器
除了可以模拟方法,数据,还可以模拟定时器,因为真正的测试不可能等代码中所有的定时器都执行完,那会花费大量的时间。
- jest.useFakeTimers(); //遇到setTimoeut等定时器就是用模拟的timers
- 在调用有timer的函数的时候加上jest.runAllTimers(),它会立即将timer立即完成。
- runAllTimers会将所有的timer都立即完成,而runOnlyPedingTimers(),将当前处于队列中的timer完成。
- jest.advanceTimersByTime(3000) 让时间快进3s
const fetchData = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ success: true });
}, 5000);
});
beforeEach(()=>{
jest.useFakeTimers();
})
afterEach(() => {
jest.useRealTimers();
});
如上,在每一个test执行之前,就需要初始化使用假的timer。然后结束的时候需要手动清理。
test("模拟定时器", (done) => {
fetchData()
.then((res) => {
expect(res).toEqual({ success: true });
done();
})
.catch((err) => {
done(err);
});
jest.runAllTimers()
jest.runOnlyPedingTimers()
jest.advanceTimersByTime(10000);
});
快照
快照,顾名思义,就是现在拍照,然后过一阵时间再拍照,对比两个的差异。jest的快照也是如此
假设有个方法
const generateConfig = () => {
return {
server: "http://localhost",
port: 8080,
};
};
传统的测试
test('测试generateConfig',()=>{
expect(generateConfig()).toEqual({
server: 'http://localhost',
port:8080
})
})
快照测试
test('测试generateConfig',()=>{
expect(generateConfig()).toMatchSnapshot({
time: expect.any(Date)
});
})
第一次执行这个test的时候,会在根目录下创建_snapshots_文件,保存着你的快照
如
exports[`测试generateConfig 1`] = `
Object {
"port": 8080,
"server": "http://localhost",
}
`;
第二次执行的时候,就会跟之前保存的快照进行比较。如果不同就报错。
但是如上,time: new Date() 等动态数据怎么执行都不可能跟上次一样,所以jest提供了参数,如
expect(generateConfig()).toMatchSnapshot({
time: expect.any(Date)
});
只要time匹配到一个Date的类型就可以。
此外,如果遇到不一样的快照,可以输入命令行工具
u 可更新快照全部
i 交互式的一个一个快照进行更新
toMatchInlineSnapshot行内快照,即不生成snapshot文件夹,而是将快照内嵌在test.js文件中,需要搭配安装prettier@1.18.2。如
test("测试generateConfig", () => {
expect(generateConfig()).toMatchInlineSnapshot(
{
time: expect.any(Date)
},
);
});
第一次执行之后
test("测试generateConfig", () => {
expect(generateConfig()).toMatchInlineSnapshot(
{
time: expect.any(Date)
},
`
Object {
"port": 8080,
"server": "http://localhost",
"time": Any<Date>,
}
`
);
});
这就是快照的基本用法。
dom
import * as jsdom from 'jsdom'
var $ = require('jquery')(new jsdom.JSDOM().window);
const addDivToBody = () => {
$("body").append("<div>123</div>");
};
test("测试 addDivToBody", () => {
addDivToBody();
addDivToBody();
expect($("body").find('div').length).toBe(2)
});
jest在node环境下自己模拟了一套dom的api, jsdom,所以直接使用dom的一些操作。
|