美文网首页
Jest 实践

Jest 实践

作者: 玩问 | 来源:发表于2021-08-09 17:48 被阅读0次

    Jest 作为单元测试框架提供整个测试环境,包括except,assert语法,mock模块,代码覆盖率的能力。

    1. 安装

    yarn add --dev jest //yarn 
    npm install --save-dev jest //npm
    

    安装之后,根目录下运行 jest test,就会开始跑单元测试,默认会匹配以下路劲的文件[**/__tests__/**/*.[jt]s?(x), **/?(*.)+(spec|test).[tj]s?(x)]。通常情况下会将单元测试文件放到__test__目录下。

    2. 生成基础配置文件

    Jest通过命令行的方式生成一个配置文件jest.config.js,用来控制Jest的行为。

    jest --init
    

    执行该命令时会问以下几个问题:

    问题 选项 说明 备注
    Automatically clear mock calls and instances between every test? yes/no 是否在每个单元测试前自动清除模拟调用 通常选yes
    Choose the test environment that will be used for testing jsdom(browser-like) or node 选择运行环境 如果涉及浏览器相关的操作,选择jsdom
    Do you want Jest to add coverage reports? yes/no 是否生成测试覆盖率报告 通常选yes,可以生成覆盖率报告
    Which provider should be used to instrument code for coverage? V8 or babel
    Would you like to use Jest when running "test" script in "package.json"? yes/no 是否在package.json的script添加test命令 选择yes后,直接运行npm run test 就可以跑单元测试
    Would you like to use Typescript for the configuration file? yes/no 配置文件是否用ts格式 项目中不用ts, 选no即可

    常用配置

    module.exports = {
    
     //是否将导入的模块自动mock,通常不要,因为有些mock逻辑是你可能想自定义
      automock: false,
    
      //是否在每次测试都清楚mock, 通常选true
      clearMocks: true,
    
    //哪些文件要收集单测报告,通常根据文件自定义
      collectCoverageFrom: ['src/components/**/*.{js,jsx,ts,tsx}', 'src/hooks/**/*.{js,jsx,ts,tsx}', 'src/utils/**/*.{js,jsx,ts,tsx}'],
    
     //覆盖率报告的文件名称,默认coverage就好
      coverageDirectory: "coverage",
    };
    
    

    更多配置查看:https://jestjs.io/zh-Hans/docs/configuration
    查看更多命令,执行jest help

    3. 添加测试相关命令

    package.json 中 script添加单元测试相关命令

    "script": {
        "test": "jest test --coverage", 
        "test:update": "jest test --updateSnapshot",
        "test:watch": "jest test  --watch",
         "test:verbose": "jest test  --verbose",
    }
    

    注:无cross-env

    "script": {
        "test": "cross-env BABEL_ENV=test jest --coverage",
        "test:update": "cross-env BABEL_ENV=test jest --updateSnapshot",
        "test:watch": "cross-env BABEL_ENV=test jest  --watch"
        "test:verbose": "cross-env BABEL_ENV=test jest  --verbose"
    }
    

    注:前提安装了cross-env
    --coverage 执行之后可以在命令行中生成覆盖率报告以及在根目录下生成coverage文件。

    屏幕快照 2021-08-06 上午11.33.46.png
    关于覆盖率的知识:http://www.ruanyifeng.com/blog/2015/06/istanbul.html
    浏览器打开coverage/lcov-report/index.html,可以看到每个文件的具体覆盖率,点开某个个文件,对于没有覆盖的地方,会用不同的颜色标记出来。
    屏幕快照 2021-08-06 下午2.14.01.png
    --updateSnapshot,更新快照,当快照发生变化时,确认快照需要变化,可以运行该命令。
    --watch 观察者模式,每次文件变更会自动触发重新跑单元测试,通常在开发过程中使用,方便实时查看。
    --verbose,层次显示测试套件中每个测试的结果,方便查看每个用例具体是什么。
    屏幕快照 2022-03-17 下午2.27.15.png

    更多的命令查看:https://jestjs.io/zh-Hans/docs/cli

    4.使用Babel

    需要测试ECMAScript2015+ 的代码,需要安装@babel/preset-env

    yarn add --dev babel-jest @babel/core @babel/preset-env //yarn
    npm install --save-dev babel-jest @babel/core @babel/preset-env  //npm
    

    在工程的根目录下创建一个babel.config.js文件用于配置与你当前Node版本兼容的Babel

    module.exports = {
      presets: [['@babel/preset-env', {targets: {node: 'current'}}]],
    };
    

    5. 一些基本用法

    1. 基本概念
      describe(name, fn)
      describe块称为"测试套件"(test suite),表示一组相关的测试。它是一个函数,第一个参数是测试套件的名称("加法函数的测试"),第二个参数是一个实际执行的函数。
      test(name, fn, timeout)
      其别名为(name, fn, timeout)。test块称为"测试用例"(test case),表示一个单独的测试,是测试的最小单位。它也是一个函数,第一个参数是测试用例的名称("1 加 1 应该等于 2"),第二个参数是一个实际执行的函数。
    //跳过该测试用例
    test.skip(name, fn)
    //该测试用例标记为将要做的
    test.todo(name, fn)
    //只运行该测试用例
    test.only(name, fn)
    
    //__test__/sum.js
    const sum = (a, b) => {
        return a+b
    }
    export default sum
    
    //sum.test.js
    import sum from '../sum'
    describe('加法函数的测试', () => {
      test('1 加 1 应该等于 2', () => {
        expect(sum(1,1)).toBe(2);
      });
    
      test('1+1不等于3', () => {
        expect(sum(1,1)).not.toBe(3);
      });
    });
    
    1. 钩子函数
    beforeEach(fn, timeout)//每个测试用例执行前执行
    afterEach(fn, timeout)//每个测试用例执行后执行
    beforeAll(fn, timeout)//所有测试用例测试之前执行
    afterAll(fn, timeout)//所有测试用例测试之后执行
    

    更多全局函数参考:https://jestjs.io/zh-Hans/docs/api

    1. 断言
      expect函数+ matcher函数断言
      3.1 expect函数
    expect(value) //后边可以跟匹配器
    expect.anything() //匹配除了null和undefined的所有,可以检查是否使用非空参数调用模拟函数
    expect.any(constructor) //匹配任何构造器
    expect.objectContaining(object)
    expect.not.objectContaining(object)
    expect.arrayContaining(array) //匹配一个数组包含
    expect.not.arrayContaining(array)
    expect.stringContaining(string)
    expect.not.stringContaining(string)
    expect.extend(matchers) //自定义匹配器,想要了解jest匹配器实现的可以看这一部分
    expect.assertions(number) //验证断言次数
    expect.hasAssertions() //验证至少有一个断言
    

    注:按日常使用频率排序
    expect.any-expect.not.stringContaining 这几个通常搭配toBeCalledWith一起使用

    //objectContaining常可以用来校验函数的入参
    test('onPress gets called with the right thing', () => {
      const onPress = jest.fn();
      simulatePresses(onPress);
      expect(onPress).toBeCalledWith(
        expect.objectContaining({
          x: expect.any(Number),
          y: expect.any(Number),
        }),
      );
    });
    

    3.2 常用匹配器(Matcher)

    toBe(value)//匹配确定值
    toContain(item)//匹配数组的某一个
    toEqual(value) //深度匹配
    toHaveLength(number)//匹配长度
     
    toBeCalled() //匹配函数被调用
    toHaveBeenCalledTimes(number) //匹配函数调用次数
    toHaveBeenCalledWith(arg1, arg2, ...)//匹配函数调用的参数
    toHaveBeenLastCalledWith(arg1, arg2, ...)//匹配最后一次调用的返回值
     
    toBeFalsy()//匹配假
    toBeTruthy()//匹配真
    toBeNull()//匹配空
    toBeUndefined()//匹配Undefined
    
    toThrow(error?) //匹配错误
    
    toBeGreaterThan(number | bigint) //匹配大于
    toBeGreaterThanOrEqual(number | bigint) //匹配大于等于
    toBeLessThan(number | bigint) //匹配小于
    toBeLessThanOrEqual(number | bigint) //匹配小于等于
     
    toMatchSnapshot(propertyMatchers?, hint?) 生成快照文件,
    toMatchInlineSnapshot(propertyMatchers?, inlineSnapshot) 行内匹配快照
     
    not //取反
    

    更多断言查阅:https://jestjs.io/zh-Hans/docs/expect
    关于快照的使用,可以查看:https://jestjs.io/zh-Hans/docs/snapshot-testing

    1. mock函数
      函数和模块是我们项目中重要的组成部分,在我们对一个函数或模块的测试时,可能依赖其他的函数和模块,为了避免干扰,我们需要把影响我们测试的其它变成变成固定不变的,因此需要mock。
    //mock一个函数
    jest.fn()
     
    const mockFn = jest.fn()
    mockFn.mockClear()//清除mock
    mockFn.mockImplementation(fn) //mock函数实现
    mockFn.mockImplementationOnce(fn)
    mockFn.mockReturnValue(value)//mock返回值
    mockFn.mockReturnValueOnce(value)
    mockFn.mockResolvedValue(value) //mock异步函数Resolved时返回值
    mockFn.mockResolvedValueOnce(value)
    mockFn.mockRejectedValue(value) //mock异步函数Rejected时返回值
    mockFn.mockRejectedValueOnce(value)
     
    #mock 模块
    jest.mock(...) //模块路径
    

    更多:https://jestjs.io/zh-Hans/docs/mock-function-api
    关于异步的测试,查看:https://jestjs.io/zh-Hans/docs/tutorial-async

    1. mock定时器
      如果被测试的文件包含定时器的功能,我们总不能等待指定的之间之后再去执行一些东西,因此我们需要对定时器相关的东西做些操作,已加快执行,消除等待时间。
    jest.useFakeTimers() //使用mock的定时器,通常在beforeEach中调用
    jest.useRealTimers() //使用真实的定时器,通常在afterEach中调用
    jest.runAllTimers() //运行所有的定时器,加速定时器的运行
    jest.clearAllTimers() //清除当前所有挂载的定时器
    jest.runOnlyPendingTimers() //执行当前挂载的定时器
    jest.advanceTimersByTime(ms) //提前XXms执行定时器
    

    更多例子查看:https://jestjs.io/zh-Hans/docs/timer-mocks


    好啦,Jest的基本知识到这里就结束了,如果想要了解更多的知识,还是需要去查Jest官网,亦或者想更深入得了解,则是去查看源码。如果不涉及DOM的操作,那么单元测试的基本东西可以到此结束,但是前端通常都是需要和DOM打交道的,接下来来就来讲讲怎样测试DOM, react-testing-libraryenzyme究竟怎么选。

    相关文章

      网友评论

          本文标题:Jest 实践

          本文链接:https://www.haomeiwen.com/subject/jnrelltx.html