当前位置: 首页 > 后端技术 > Node.js

使用Jest测试JavaScript(Mock篇)

时间:2023-04-03 20:47:37 Node.js

本教程将介绍Jest中Mock函数相关的三个API,分别是jest.fn()、jest.spyOn()、jest.mock()。使用它们创建Mock函数,可以帮助我们更好的测试项目中一些逻辑复杂的代码,比如测试函数的嵌套调用,回调函数的调用等。如果你不了解Jest的基本用法,请阅读:《使用Jest测试JavaScript (入门篇)》为什么要用Mock函数?在一个项目中,一个模块的方法经常调用另一个模块的方法。在单元测试中,我们可能不需要关心内部调用方法的执行过程和结果,只想知道是否调用正确,甚至可以指定函数的返回值。这时候就非常有必要使用Mock功能了。Mock函数提供的以下三个特性在我们编写测试代码时非常有用:捕获函数调用、设置函数返回值、更改函数内部实现、然后使用上一篇文章中的目录结构,在test/functions.test.js文件中写入测试代码,测试代码写入src/目录下。1.jest.fn()jest.fn()是创建Mock函数最简单的方法。如果未定义函数的内部实现,jest.fn()将返回undefined作为返回值。//functions.test.jstest('Testjest.fn()call',()=>{letmockFn=jest.fn();letresult=mockFn(1,2,3);//断言执行mockFnThenreturnundefinedexpect(result).toBeUndefined();//AssertthatmockFnwascalledexpect(mockFn).toBeCalled();//AssertthatmockFnwascalledonceexpect(mockFn).toBeCalledTimes(1);//断言即mockFn传入的参数为1,2,3expect(mockFn).toHaveBeenCalledWith(1,2,3);})jest.fn()创建的Mock函数也可以设置返回值,定义内部实现或者返回承诺对象。//functions.test.jstest('Testjest.fn()returnsafixedvalue',()=>{letmockFn=jest.fn().mockReturnValue('default');//断言返回值mockFn被执行defaultexpect(mockFn()).toBe('default');})test('测试jest.fn()的内部实现',()=>{letmockFn=jest.fn((num1,num2)=>{returnnum1*num2;})//断言mockFn在执行后返回100expect(mockFn(10,10)).toBe(100);})test('testjest.fn()returnsPromise',async()=>{letmockFn=jest.fn().mockResolvedValue('default');letresult=awaitmockFn();//通过await关键字expect执行后断言mockFn的返回值为default(result).toBe('default');//断言mockFn调用返回一个Promise对象expect(Object.prototype.toString.call(mockFn())).toBe("[objectPromise]");})以上代码由jest.fn()提供了几个常用的API和断言语句,下面我们在src/fetch.js文件中编写一些测试代码,以更贴近业务的方式了解Mock功能的实际应用。测试代码依赖于axios这个常用的请求库,以及上一篇文章中提到的免费请求接口JSONPlaceholder。请在shell中执行npminstallaxios--save来安装依赖。//fetch.jsimportaxiosfrom'axios';exportdefault{asyncfetchPostsList(callback){returnaxios.get('https://jsonplaceholder.typicode.com/posts').then(res=>{returncallback(res.data);})}}我们封装了一个fetch.js中的fetchPostsList方法,请求JSONPlaceholder提供的接口,通过传入的回调函数返回处理后的返回值。如果我们要测试接口是否可以正常请求,只需要捕获传入的回调函数可以正常调用即可。下面是functions.test.js中的测试代码。importfetchfrom'../src/fetch.js'test('fetchPostsList中的回调函数应该可以调用',async()=>{expect.assertions(1);letmockFn=jest.fn();awaitfetch.fetchPostsList(mockFn);//AssertmockFniscalledexpect(mockFn).toBeCalled();})2.jest.mock()封装在fetch.js文件夹中的请求方法可能会被其他模块调用,不需要发出实际请求(请求方法已被单方面传递或要求方法返回非真实数据)。此时,就需要使用jest.mock()来mock整个模块。下面我们在与src/fetch.js相同的目录下创建一个src/events.js。//events.jsimport从'./fetch'获取;exportdefault{asyncgetPostList(){returnfetch.fetchPostsList(data=>{console.log('fetchPostsListbecalled!');//做点什么});functions.test.js中的测试代码如下://functions.test.jsimporteventsfrom'../src/events';从'../src/fetch'导入提取;jest.mock('../src/fetch.js');test('模拟整个fetch.js模块',async()=>{expect.assertions(2);awaitevents.getPostList();expect(fetch.fetchPostsList).toHaveBeenCalled();期望(fetch.fetchPostsList).toHaveBeenCalledTimes(1);});在测试代??码中,我们使用jest.mock('../src/fetch.js')来模拟整个fetch.js模块。如果将这行代码注释掉,在执行测试脚本的时候会出现如下错误信息。从这个报错信息中,我们可以得出一个重要的结论:如果你想在jest中捕获一个函数的调用,这个函数必须被mock或spyed!3.jest.spyOn()jest.spyOn()方法同样创建了一个mock函数,但是mock函数不仅可以捕获函数的调用,还可以正常执行spyed函数。事实上,jest.spyOn()是jest.fn()的语法糖,它创建了一个模拟函数,其内部代码与被监视的函数相同。上图是之前jest.mock()示例代码中正确执行结果的截图。从shell脚本中,您可以看到console.log('fetchPostsListbecalled!');这行代码没有打印在shell中。这是因为通过jest.mock()后,模块中的方法并不会被jest真正执行。这时候我们就需要用到jest.spyOn()。//functions.test.jsimporteventsfrom'../src/events';importfetchfrom'../src/fetch';test('使用jest.spyOn()监听fetch.fetchPostsList被正常调用',async()=>{expect.assertions(2);constspyFn=jest.spyOn(fetch,'fetchPostsList');awaitevents.getPostList();expect(spyFn).toHaveBeenCalled();expect(spyFn).toHaveBeenCalledTimes(1);})执行npmruntest后,可以在shell中看到打印信息,说明已经通过jest.spyOn()正常执行了fetchPostsList。4.总结在本文中,我们介绍了jest.fn()、jest.mock()和jest.spyOn()来创建模拟函数。通过mock函数,我们可以利用以下三个特性来更好的编写我们的测试代码:捕捉函数调用、设置函数返回值、改变函数内部实现在实际项目的单元测试中,jest.fn()经常用于一些使用回调函数进行测试;jest.mock()可以mock整个模块在方法中,当一个模块已经被单元测试100%覆盖时,需要使用jest.mock()来mock模块以节省测试时间和测试冗余;当需要测试某些必须完全执行的方法时,通常需要使用jest.spyOn()。这些都需要开发者根据实际业务代码灵活选择。