Jest 是个开源的基于Jasmine 框架的JavaScrit 单元测试框架。
Jest的目标是减少开始测试一个项目所要花费的是件和认知负荷。
Jest的特点,易于组织,快速响应,测试快照。
安装 Jest
创建 hw.js文件 文件内容如下:
1 2 3 4 function hw ( ) { return 'Hello World!' }; module .exports = hw;
创建测试文件 hw.test.js:
1 2 3 4 const hw = require ('./hw' );test('test hw function' ,()=> { expect(hw()).toBe("Hello World!" ); })
Jest默认会查找 项目文件中后缀为.test.js的文件,进行执行。 项目中我们把测试文件 写在 __tests__ 文件夹下。
使用Babel需安装 babel-jest
1 npm i babel-preset-env babel-jest -D
如果你没有使用Yarn 或者Npm版本低于3,你还需要安装 regenerator-runtime
1 npm i regenerator-runtime -D
最后需要配置.babelrc文件
当我们用Webpack构建项目时,我们经常将css,字体引入到js文件中,然后通过Webpack打包对这些静态资源进行处理。 Jest 无法解析这些静态资源,需要通过Jest配置Mock这些静态资源。 Jest 配置如下: package.json
1 2 3 4 5 6 7 { "jest" :{ "moduleNameMapper" :{ "\\.css$" :"<rootDir>/__mock__/styleMock.js" } } }
安装相关依赖包
1 npm i ts-jest @types/jest typescript -D
配置 package.json
1 2 3 4 5 6 7 8 9 { "jest" :{ "transform" :{ ".(ts|tsx)" :"<rootDir>/node_modules/ts-jest/preprocessor.js" , }, "testRegex" :"(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$" , "moduleFileExtensions" :["ts" ,"tsx" ,"js" ,"json" ] }
Describe(name, fn) 测试套件,一组相关的测试用例。第一个参数是测试套餐的描述,第二个参数是测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13 const my = { name : "fynn" , age : 27 } describe("my info" , ()=> { test("my name" , ()=> { expect(my.name).toBe("fynn" ) }); test("my age" , ()=> { expect(my.age).toBe(27 ) }) })
Describe.only(name, fn) 当一个file有多个测试套件,但你只想执行其中一个测试套件,可以使用 describe.only
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const my = { name : "fynn" , age : 27 } let hw = () => "Hello World!" ;describe("my info" , ()=> { test("my name" , ()=> { expect(my.name).toBe("fynn" ) }); test("my age" , ()=> { expect(my.age).toBe(27 ) }) }); describe.only('hw function test suit' ,()=> { test('hw test' ,()=> { expect(hw()).toBe("Hello World!" ); }) })
Describe.skip(name, fn) 一个file中有多个测试套件,如果你想跳过某个测试套件可以使用 describe.skip
Test 测试用例,可以写在 describe测试套件中,也可以单独写在测试套件外面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const my = { name : "fynn" , age : 27 } let hw = ()=> "Hello World!" describe("my info" ,()=> { test("my name" ,()=> { expect(my.name).toBe("fynn" ) }); test("my age" ,()=> { expect(my.age).toBe(27 ) }) }); test("hw test" ,()=> { expect(hw()).toBe("Hello World!" ); })
Test.only 有多个测试用例或测试套件,只想执行其中某一个测试用例时可以用test.only。
1 2 3 4 5 6 7 8 9 10 11 const my = { name : "fynn" , age : 27 }; let hw = ()=> "Hello World!" ;test("my name" ,()=> { expect(my.name).toBe("fynn" ); }) test.only("hw test" ,()=> { expect(hw()).toBe("Hello World!" ); })
Test.skip(name, fn) 当有多个测试用例,想跳过某个测试用例可以使用test.skip;
It(name,fn) 用法和test一样,不能嵌套在test中!可以嵌套在describe中
1 2 3 4 5 6 7 8 9 10 11 const my = { name : "fynn" , age : 27 }; let hw = ()=> "Hello World!" ;it("my name" ,()=> { expect(my.name).toBe("fynn" ); }) xit("hw test" ,()=> { expect(hw()).toBe("Hello World!" ); })
xit 类似与test.skip,跳过这个测试实例。
AfterAll(fn) 当file所有test都执行完毕后,执行afterAll中的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 const my = { name :"fynn" , age : 27 }; test("my name" ,()=> { expect(my.name).toBe("fynn" ) }); test("my age" ,()=> { expect(my.age).toBe(27 ) }); afterAll(()=> { console .log("执行完所有test!" ) })
AfterEach(fn) 每当一个test执行完后,调用一次afterEach中的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 const my = { name :"fynn" , age : 27 }; test("my name" ,()=> { expect(my.name).toBe("fynn" ) }); test("my age" ,()=> { expect(my.age).toBe(27 ) }); afterEach(()=> { console .log("执行完一个test!" ) })
BeforeAll(fn) 在所有执行test前先调用beforeAll中的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 const my = { name :"fynn" , age : 27 }; test("my name" ,()=> { expect(my.name).toBe("fynn" ) }); test("my age" ,()=> { expect(my.age).toBe(27 ) }); beforeAll(()=> { console .log("要开始执行test了!" ) });
BeforeEach(fn) 在每个test执行前都会调用一次beforeEach中的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 const my = { name :"fynn" , age : 27 }; test("my name" ,()=> { expect(my.name).toBe("fynn" ) }); test("my age" ,()=> { expect(my.age).toBe(27 ) }); beforeEach(()=> { console .log("要开始执行一个test了!" ) })
Jest提供了很多适配器用于比较不同类型的测试值是否正确。
通用的Matchers 1 2 3 test("two plus two is four" , ()=> { expect(2 +2 ).toBe(4 ) })
toBe 用来匹配expect值类型number值或string值的返回值。 toBe 使用 ‘===’测试值是否相等。
1 2 3 4 5 6 7 8 test('Object assignment' , ()=> { const data = {one :1 }; data["two" ] = 2 ; expect(data).toEqual({ one : 1 , two : 2 }) })
toEqual 用于比较每个数组或对象中每个字段。 toEqual 字段值的比较也是用“===”。
null、undefined、boolean匹配 有时候需要测试我们的值是否是 null、undefined、false、true等特殊值。Jest提供这些值的匹配方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 toBeNull --- 只匹配 null toBeUndefined --- 只匹配 undefined toBeDefined --- 匹配 非undefined 值 toBeTruthy --- 匹配所有 if 状态是 true 值 toBeFalsy --- 匹配所有if 状态是false 值 test("null,undefined,boolean" ,()=> { const n = null ; const u = undefined ; const t = 'str' ; const f = 0 ; expect(n).toBeNull(); expect(u).toBeUndefined(); expect(t).toBeTruthy(); expect(n).toBeFalsy(); })
Numbers Jest提供了数值大小的匹配器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 toBeGreaterThan --- 大于 toBeGreaterThanOrEqual --- 大于或等于 toBeLessThan --- 小于 toBeLessThanOrEqual --- 小于或等于 toBe --- 等于 toEqual --- 等于 test("one plus two" ,()=> { const value = 1 +2 ; expect(value).toBeGreaterThan(2.5 ); expect(value).toBeGreaterThanOrEqual(3 ); expect(value).toBeLessThan(3.5 ); expect(value).toBeLessThanOrEqual(3 ); expect(value).toBe(3 ) expect(value).toEqual(3 ); })
进行浮点型计算时,使用__toBeCloseTo__ 进行值的比较
1 2 3 4 5 6 test("adding floating point numnber" ,()=> { cosnt value = 0.1 +0.2 ; expect(value).toBeCloseTo(0.3 ); expect(value).toBe(0.3 ) expect(value).not.toBe(0.3 ) })
Stings 字符串正则表达式匹配器使用__ToMatch__
1 2 3 4 5 6 test("there is no I in team" ,()=> { expect('team' ).not.toMatch(/I/ ); }) test('but there is a "stop" in Christoph' , ()=> { expect("Christoph" ).toMatch(/stop/ ) })
匹配字符串长度使用__toHaveLength__
1 2 3 test('"abc" is length' ,()=> { expect("abc" ).toHaveLength(3 ); })
toHaveLength 也可以匹配object中有 .length属性的值。
1 2 3 test("arr is length" ,()=> { expect([1 ,2 ,3 ]).toHaveLength(3 ) })
Arrays 匹配数组中某个值使用__toContain__
1 2 3 4 const shoppingList=["diapers" ,"kleenex" ,"trash bags" ,"paper towels" ,"beer" ];test("the shopping list has beer on it" ,()=> { expect(shoppingList).toContain('beer' ); })
匹配某个数组是否是另一个数组的子集使用__expect.arrayContaining(array)
1 2 3 4 test("arrayContaining" ,()=> { const expected = [2 ,3 ]; expect([1 ,2 ,3 ,4 ,5 ,6 ]).toEqual(expect.arrayContaining(expected)) })
实际开发中我们经常会进行异步操作,Jest提供了三种进行异步操作测试的方式。
Callbacks 在callback方式中的测试实例第二个执行函数会有个参数__done__,只有当done被调用这个测试实例才算完结。
1 2 3 4 5 6 7 8 test('the data is peanut butter' ,done => { function callback (data ) { expect(data).toBe('peanut butter' ); done(); } fetchData(callback) })
如果done() 未被调用,这个测试实例就算失败。
Promises 如果你使用promises,在你的测试实例回调函数返回一个promise,Jest会等待这个promise状态变为resolve。如果promise状态变为rejected,这个测试实例会自动变成fail。
1 2 3 4 5 test('the data is peanut butter' ,()=> { return fetchData().then(data => { expect(data).toBe('peanut butter' ); }) })
也可以直接用__resolves__将放回结果进行验证。
1 2 3 test('the data is peanut butter' ,()=> { return expect(fetchData()).resolves.toBe('peanut butter' ); })
Async/Await 使用async 定义测试实例回掉函数,用await等待异步执行。
1 2 3 test('the data is peanut butter' ,async ()=>{ await expect(fetchData()).resolves.toBe('peanut butter' ); })
Mock函数可以记录下一个函数执行的信息(参数,实例对象)。
创建Mock函数 1 const mockFn = jest.fn();
mockFn 如果没有传递实例函数,它默认返回 undefined
mockFn.mock.calls 返回MockFn实例对象,实例化传递过的参数,以数组方式。
1 2 3 4 5 const mockFn = jest.fn((a,b )=> `${a} --- ${b} ` );var f1 = new mockFn('f1-arga' ,'f1-argb' )var f2 = new mockFn('f2-arga' ,'f2-argb' )var calls = mockFn.mock.calls();
mockFn.mock.instances 返回mockFn 实例过的对象,以数组方式。
1 2 3 4 5 6 7 const mockFn = jest.fn();const a = new mockFn();const b = new mockFn();mockFn.mock.instances[0 ] === a; mockFn.mock.instances[1 ] === b;
mockFn.mockImplementation(fn) 接受一个函数,它会做为mock的实例方法。这个mock还是可以记录对他调用所有使用的参数。
1 2 3 4 5 6 7 8 9 const mockFn = jest.fn().mockImplementation(scalar =>42 +scalar);const a = mockFn(0 );const b = mockFn(1 );a === 42 b === 43 mockFn.mock.calls[0 ][0 ] === 0 ; mockFn.mock.calls[1 ][0 ] === 1 ;
mockImplementation 也可以用于类的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 export class SomeClass { m (a,b) {}}import SomeClass from './someClass' const mMock = jest.fn();SomeClass.mockImplementation(()=> { return {m :mMock} }) const some = someClass();some.m('a' ,'b' ); console .log('Calls to m:' , mMock.mock.calls)
mockImplementation 一般用户自定义个月mock实例,替换调引用进来的原实例方法
1 2 3 4 5 6 7 8 export function foo ( ) {return 40 };import foo from './foo' jest.mock('./foo' ) foo.mockImplementation(()=> 42 ); foo();
mockFn.mockImplementationOnce(fn) 通过 mockImplementationOnce mock可以接受多个实例方法。当调用mock时,依次调用定义好的实例方法,当调用次数超过 mockImplementationOnce 次数,则返回默认实例方法值,如果没有定义默认实例方法则返回undefined
1 2 3 4 5 6 var myMockFn = jest.fn() .mockImplementationOnce(cb => cb(null ,true )) .mockImplementationOnce(cb => cb(null ,false )) myMockFn((err, val )=> console .log(val)); myMockFn((err, val )=> console .log(val));
在开发时我们需要调用后台数据,可以通过创建manual mock模拟后台数据,这样使得测试更容易更快。 Manual Mocks模块应写在文件夹__mocks__下,__mocks__ 文件夹应该和被mock的模块放在一起
例子 1 2 3 4 5 6 7 8 9 10 . ├── config ├── __mocks__ │ └── fs.js ├── models │ ├── __mocks__ │ │ └── user.js │ └── user.js ├── node_modules └── views
__mocks__/user.js mock user.js例子源码
Jest 配置项 collectCoverage
用来设置是否启用测试覆盖报告. 如果启用则会增加执行单元测试用例时间.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 module .exports = { preset : "@vue/cli-plugin-unit-jest/presets/typescript-and-babel" , testMatch : ["**/test/**/*.spec.[jt]s?(x)" ], moduleFileExtensions : ["js" , "ts" , "vue" ], moduleNameMapper : { "^@ModelApp/(.*)$" : "<rootDir>/src/$1" }, collectCoverage : true , collectCoverageFrom : [ "./src/store/**/*.{js,ts}" , "./src/modules/**/*.{js,ts}" , "./src/utils/**/*.{js,ts}" , "./src/common/**/*.{js,ts}" , "!**/node_modules/**" ] }
stmts(Statements)是语句覆盖率(statement coverage):是不是每个语句都执行了?
Branch(Branches)分支覆盖率(branch coverage):是不是每个if代码块都执行了?
Funcs(Functions)函数覆盖率(function coverage):是不是每个函数都调用了?
Lines行覆盖率(line coverage):是不是每一行都执行了?
collectCoverageFrom
用来设置 单元测试报告覆盖的 文件目录.
1 2 3 4 5 6 7 { "collectCoverageFrom" : [ "**/*.{js,jsx}" , "!**/node_modules/**" , "!**/vendor/**" ] }
参考: https://facebook.github.io/jest/docs/getting-started.html