RN 基于 Jest + testing-library 单元测试实践(1)

发表于:2021-9-16 09:40

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:清香的orange    来源:掘金

  技术选型
  测试工具选型:Jest + testing-library
  1、jest 是一个开源的 javascript 单元测试框架,集成了测试执行器、断言库、spy、mock、snapshot和测试覆盖率报告等功能。
  2、@testing-library 是用于 Dom 和 UI 组件测试的工具,提供了一系列常用的测试 API。
  注意:react-native-testing-library 已经转移到 @test-library/react-native
  环境搭建
  安装 Jest
  npm i jest@27.0.2
  npm i babel-jest@27.0.2// babel进行转码
  npm i ts-jest@27.0.2// ts语法解析
  npm i @types/jest// ts语法定义
  说明
  项目中如果使用的 TypeScript,需要安装 ts-jest、@types/jest,并在 tsconfig.json 文件中加入 "types": ["jest"]
  注意:jest、ts-jest、babel-jest 三个依赖包的版本需要一致,否则可能会报错。
  安装 testing-library
  npm i @testing-library/jest-dom @testing-library/react-native @testing-library/react-hooks -D
  说明
  ·@testing-library/jest-dom提供了一组可用于扩展 jest 的自定义 jest 匹配器。这些将使您的测试更具声明性,更易于阅读维护。
  · @testing-library/react-native用于测试 React Native 组件。(如果测试React,请选择@testing-library/react)
  · @testing-library/react-hooks为React Hook创建一个简单的测试工具,并在函数组件体内运行。可选择性下载该项。
  package.json 文件配置
  "scripts": {
    ...
    "test": "jest test --verbose -u --watch",
  }
  "jest": {
  "preset": "react-native"
  }
  babel 文件配置
  ·如果项目中 bebel 配置使用的是 .babelrc.js,需要将文件转为 babel.config.js(可能会影响项目原本编译结果)
  ·如果转化为 babel.config.js 文件影响项目编译结果,可以保留 .babelrc.js 文件,然后新建 babel.config.js 文件并将 presets 声明移入 babel.config.js
  示例:
  // babel.config.js
  module.exports = (api) => {
    api.cache.never();
    return {
      "presets": process.env.MINI_PROGRAM === 'true' ? [] : ['module:metro-react-native-babel-preset'],
    }
  };
  jest.config.js 文件配置
  项目根目录新建 jest.config.js,配置可参考如下(笔者项目使用的是 ts 的语法,js 等配置可自行添加)。
  const { defaults } = require('ts-jest/presets');
  module.exports = {
    ...defaults,
    preset: 'react-native',
    globals: {
      'ts-jest': {
        babelConfig: true,
      },
    },
    // 定义测试的文件目录,与配置文件同级目录下的 tests 文件夹内的 .tsx/.jsx 后缀名文件
    testRegex: '(/tests/.*\\.(test|spec))\\.[tj]sx?$',
    // 定义文件的编译方式
    transform: {
      '^.+\\.tsx?$': 'ts-jest',
    },
    // 定义了忽略进行 jest 执行的依赖包
    transformIgnorePatterns: [
      'node_modules/(?!(react-native|@testing-library|react-navigation|@react-navigation/.*|@react-native-community)/)',
    ],
    testPathIgnorePatterns: ['<rootDir>/node_modules/', '\\.snap$'],
    // 缓存文件生成的目录地址,注意在.gitignore 中忽略
    cacheDirectory: '.jest/cache',
    testEnvironment: 'jsdom',
    moduleNameMapper: {
      '^[@./a-zA-Z0-9$_-]+\\.(png|gif)$': '<rootDir>/node_modules/react-native/Libraries/Image/RelativeImageStub',
    },
  };
  基础知识
  简单例子
  常见的一个测试文件如下:
  // sum.test.ts
  const sum = (a, b) => {
  return a + b;
  }
  describe('Unit test explain', () => {
  test('sum success', () => {
      expect(sum(1, 2)).toBe(3);
    })
  })
  ·describe 通常将测试套件分解为组件。意思就是我们可以将某个组件的整体功能进行分解,按每个模块或者每个功能进行区分。而 test/it 是执行个别测试的地方,用来描述每一个测试功能点。
  · test 称为测试用例,接收两个参数。第一个参数是该用例的描述,第二个是测试函数,用来定义逻辑。与 it 同义。
  · expect 是期望的意思,整行称为断言。上面的意思就是期望 1 + 2 能否等于 3。
  · toBe 是一个匹配器,匹配 expect 中预期的值和匹配器中的值是否相等。
  常见的匹配器
  · toBe(value) 是否完全相等,等同于 ===
  · toBeNull(value) 是否为 null
  · toBeUndefined() 是否为 undefined
  · toBeNaN() 匹配 NaN
  · toBeTruthy() 匹配 true
  · toBeFalsy()匹配 false
  · .not 后续匹配取反
  · toMatch(regexpOrString) 检查字符串是否匹配,可传字符串或者正则表达式
  · toMatchObject(object) 判断一个对象/数组是否属于子集
  · toContain(item) 匹配数组/Set/字符串中是否包含 item
  · **toContainEqual(item)  匹配数组中是否包含一个特定对象
  · toHaveProperty(keyPath, value) 匹配对象中深度嵌套的属性,判断在指定 keyPath 下是否有 value 属性
  · toHaveLength(number) 匹配对象 length 值
  · toThrow(err)/toThrowError(err) 匹配异常
  · expect 中传入函数才可以匹配到异常
  · toBeCalled()/toHaveBeCalled()  匹配函数是否被执行
  · toReturn()/toHaveReturned()  匹配函数是否有返回值
  · toReturnWith(value)/toHaveReturnedWith(value) 匹配函数返回值是否匹配
  Testing Library
  我们再来看一个例子:
  import React from 'react';
  import "setimmediate";
  import { render } from '@testing-library/react-native';
  import Button from '../lib/button';
  describe('Button unit test', () => {
  test('render success', () => {
    const { getByTestId } = render(<Button testID='button' />);
      
      // 期待找到 testID 为 button 的元素
      expect(() => getByTestId('button')).not.toThrow(/Unable to find an element with testID/);
    })
  })
  上述例子是为了测试 Button 组件是否渲染成功,并通过 testID 标识是否能找到相应的 Dom 元素。
  ·render() 用来对组件的渲染,透出的 getByTestId 方法用以接下来对 testID 的查找
  · getByTestId() 查找渲染的元素中,有无 testID 为 btn 的元素
  · toThrow(err)匹配器须传入函数才可以匹配到异常,所以我们传入() => getByTestId('btn') 并且预期不抛出无法根据 testID 找到节点的错误
  常见查询器
  先看概述:
  · getBy... 返回查询的匹配节点,如果未查找到、或者查找到多个,则抛出一个描述性的错误。
  · quertBy... 返回查询的匹配节点,如果没有匹配的元素则返回null。如果匹配到多个,则抛出错误。
  · findBy... 返回一个Promise,该Promise在找到与匹配项时进行解析。如果未匹配到元素,或者超过匹配时间(默认1000ms)时,Promise将被拒绝。
  同上,getAllBy... quertAllBy... findAllBy... 查询匹配条件的全部节点。
  常用的查询器:
  · getByRole 查询具有给定角色的元素。
  · getByLabelText 查询label与给定文本匹配的元素。
  · getByPlaceholderText 查询所有匹配占位符文本的元素。
  · getByText 查询所有文本节点与给定文本匹配的元素。
  · getByTestId 查询元素包含data-testid="${yourId}"的元素,在RN组件中,默认提供了testID属性。

  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号