最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 使用 Jest 和 Enzyme 进行 React Native 单元测试|技术点评

    正文概述 掘金(大宁的洛竹)   2021-03-11   878

    单元测试是什么 ?

    其中单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。程序单元是应用的最小可测试部件,在 React 编程中,最小单元通常是组件、函数。如果你熟悉“测试驱动开发”(TDD:Test-Driven Development),单元测试也不会陌生,狭义来说就是单测驱动开发。

    通常来说,程序员每修改一次程序就会进行最少一次单元测试,在编写程序的过程中前后很可能要进行多次单元测试,以证实程序达到软件规格书要求的工作目标,没有程序错误。在 TDD 中,甚至是先根据设计编写单元测试,然后根据单元测试写代码。

    万丈高楼平地起,单元测试和文档一样,是保障程序最小单元质量的重要一环。试想一下,一块砖可能不需要使用说明书就可以量产使用,但是一块砖不经质检测验就投入使用带来的后果可能是恐怖的。从这个角度来看,单测可能是比文档更重要的存在。当然我们也不提倡为了单测而单测,单测是为了防范于未然。

    其他测试

    前端测试常见的测试类型有单元测试(Unit testing)、集成测试(Integration testing)、端到端(E2E testing)测试,一般我们投入的测试资源排序如下:

    使用 Jest 和 Enzyme 进行 React Native 单元测试|技术点评

    集成测试是在单元测试的基础上,集成多个模块进行测试,确保模块之间互动行为正确无误的工作。有时,单一的模块完全通过单元测试,单独使用也没有问题,但是当与其他模块配合使用时,可能就出现问题了,下图是未通过集成测试的例子:

    使用 Jest 和 Enzyme 进行 React Native 单元测试|技术点评

    端到端测试是站在用户角度出发(一端)到真实运行环境(另一端)进行测试。一般我们会使用 Cypress、puppeteer 这些工具进行自动化测试以替代人肉测试。下图是未通过端到端测试的例子:

    使用 Jest 和 Enzyme 进行 React Native 单元测试|技术点评

    测试覆盖率

    我们在测试的时候,会经常关心我们的代码是否都测试到了,以及哪些代码没有测试到。jest 内置了 Istanbul 测试覆盖率工具,我们可以通过四个维度的覆盖率来了解代码测试覆盖率情况:

    • Statements(stmts):表达式覆盖率,是不是每个表达式都执行了?
    • Branches(Branch):分支覆盖率,是不是每个 if 代码块都执行了?
    • Functions(Funcs):函数覆盖率,是不是每个函数都调用了?
    • Lines(Lines):行覆盖率,是不是每一行都执行了?

    下图是执行 jest --coverage 之后生成的命令行输出:

    使用 Jest 和 Enzyme 进行 React Native 单元测试|技术点评

    下图是生成的精美的测试覆盖率报告:

    使用 Jest 和 Enzyme 进行 React Native 单元测试|技术点评

    点击 App.js 可以查看单个文件的测试覆盖率情况:

    使用 Jest 和 Enzyme 进行 React Native 单元测试|技术点评

    点开每个也没你,你会看到页面是五颜六色的,别担心,这些颜色都是有明确的意义:

    • 粉紅色的代码: 尚未被执行的 statement 或 function
    • 黄色的代码: 沒被覆盖到的 branch
    • I: 代表 if-else 的 if 没有被执行
    • E: 代表 if-else 的 if 没有被执行
    • Nx: 代表代码块被执行到的次数,可以作为代码性能的参考依据

    安装依赖

    $ yarn add jest -D
    # babel
    $ yarn add babel-jest -D
    # enzyme
    $ yarn add enzyme jest-enzyme enzyme-adapter-react-16 enzyme-to-json -D
    # react-native-mock-render
    $ yarn add react-native-mock-render -D
    # types
    $ yarn add @types/enzyme @types/jest @types/react @types/react-native -D
    

    工具介绍:

    • jest: Jest 是一个令人愉快的 JavaScript 测试框架,专注于简洁明快。
    • enzyme: Enzyme 是用于 React 的 JavaScript 测试实用程序,可以更轻松地测试 React 组件的输出。您还可以根据给定的输出进行操作,遍历并以某种方式模拟运行时。
    • jest-enzyme: 针对 enzyme 的 Jest 断言
    • enzyme-adapter-react-16: React Native 测试所需的桥接器
    • enzyme-to-json: 将 Enzyme wrappers 转换成符合 Jest 快照测试的 JSON 格式。
    • react-native-mock-render: A fully mocked and test-friendly version of react native

    配置

    jest.config.js

    module.exports = {
      preset: 'react-native',
      verbose: true,
      collectCoverage: true,
      moduleNameMapper: {
        // for https://github.com/facebook/jest/issues/919
        '^image![a-zA-Z0-9$_-]+$': 'GlobalImageStub',
        '^[@./a-zA-Z0-9$_-]+\\.(png|gif)$': 'RelativeImageStub',
      },
      setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
      snapshotSerializers: ['enzyme-to-json/serializer'],
    };
    
    • collectCoverage: 生成测试覆盖率报告
    • setupFilesAfterEnv: 使用 Jest 运行安装文件以配置 Enzyme 和适配器(如下文jest.setup.js中所示),之前是setupTestFrameworkScriptFile,也可以使用setupFiles
    • snapshotSerializers:推荐使用序列化程序使用enzyme-to-json,它的安装和使用非常简单,并允许您编写简洁的快照测试。

    jest.setup.js

    import 'react-native';
    import 'react-native-mock-render/mock';
    import 'react-native/Libraries/Animated/src/bezier'; // for https://github.com/facebook/jest/issues/4710
    import Enzyme from 'enzyme';
    import Adapter from 'enzyme-adapter-react-16';
    
    Enzyme.configure({ adapter: new Adapter() });
    

    enzyme 入门

    enzyme 是 Airbnb 开源的 react 测试类库,提供了一套简洁强大的 API,并通过 jquery 风格的方式进行 dom 处理,开发体验十分友好. 它提供三种测试方法.

    shallow

    shallow 返回组件的浅渲染,对官方 shallow rendering 进行封装。浅渲染 作用就是:它仅仅会渲染至虚拟 dom,不会返回真实的 dom 节点,这个对测试性能有极大的提升。shallow 只渲染当前组件,只能能对当前组件做断言

    mount

    mount 方法用于将 React 组件加载为真实 DOM 节点。mount 会渲染当前组件以及所有子组件。多数情况下,shallow 方法就能满足我们的需求了。ref 测试则旨在 mount 模式下生效。

    render

    render 采用的是第三方库 Cheerio 的渲染,渲染结果是普通的 html 结构,对于 snapshot 使用 render 比较合适。

    组件测试

    组件快照测试

    当我们要确保 UI 不会意外更改时,快照测试都是非常有用的工具。通过 toMatchSnapshot 即可完成。

    describe('Button Component', () => {
      it('basic render', () => {
        const component = renderer.create(<Button />).toJSON();
        expect(component).toMatchSnapshot();
      });
    });
    

    生命周期测试

    componentDidMount

    通过调用 shallowmount 方法,可以触发 componentDidMount 生命周期:

    import { shallow } from 'enzyme';
    
    function setup(props = {}) {
      const wrapper = shallow(<CarouselComponent />);
      const instance = wrapper.instance();
      return { wrapper, instance };
    }
    
    describe('Carousel Component', () => {
      it('renders correctly', () => {
        setup();
      });
    });
    

    也可以通过 wrapper.setState 方法进行触发:

    import { shallow } from 'enzyme';
    
    function setup(props = {}) {
      const wrapper = shallow(<Component {...props} />);
      const instance = wrapper.instance();
      return { wrapper, instance };
    }
    
    describe('Component', () => {
      it('renders correctly', () => {
        const { wrapper } = setup();
        wrapper.setState({
          enable: true,
        });
      });
    });
    

    componentWillUnMont

    通过调用 wrapper.unmount() 可以触发 componentWillUnMont 生命周期:

    import { shallow } from 'enzyme';
    
    function setup(props = {}) {
      const wrapper = shallow(<Component />);
      const instance = wrapper.instance();
      return { wrapper, instance };
    }
    describe('Carousel Component', () => {
      it('renders correctly', () => {
        const { wrapper } = setup();
        expect(wrapper).toMatchSnapshot();
        wrapper.unmount();
        expect(wrapper).toMatchSnapshot();
      });
    });
    

    componentWillReceiveProps

    可以通过 wrapper.setProps 方法触发:

    import { shallow } from 'enzyme';
    
    function setup(props = {}) {
      const wrapper = shallow(<Component {...props} />);
      const instance = wrapper.instance();
      return { wrapper, instance };
    }
    
    it('componentWillReceiveProps', () => {
      const { wrapper, instance } = setup({
        autoplay: true,
      });
      wrapper.setProps({ autoplay: false });
    });
    

    定时器模拟(Timer Mocks)

    原生定时器功能(即 setTimeout,setInterval,clearTimeout,clearInterval)对于测试环境来说不太理想,因为它们依赖于实时时间。 Jest 可以将定时器换成允许我们自己控制时间的功能。

    这里我们通过调用 jest.useFakeTimers() 来启用假定时器。然后在需要的时候执行 jest.runOnlyPendingTimers() 来触发定时器:

    import { shallow } from 'enzyme';
    
    jest.useFakeTimers();
    
    it('autoplay methods with count(2) and os(ios)', () => {
      const { wrapper, instance } = setup({
        autoplay: true,
        loop: false,
      });
      wrapper.setState({ isScrolling: true }, () => {
        jest.runOnlyPendingTimers();
      });
    });
    

    FAQ

    如何忽略某一块代码

    添加以下格式的注释到要忽略的代码块前即可:

    /* istanbul ignore next */
    

    使用 mount 时,忽略 React Native 的警告

    • 参考自:Remove warnings when rendering react-native components
    describe('mounting', () => {
        const origConsole = console.error;
        beforeEach(() => {
          console.error = () => {};
        });
        afterEach(() => {
          console.error = origConsole;
        });
        it ......
           mount....
    });
    

    常见 issues

    • enzyme

      • Create Adapter for React Native & React 16
      • Can't simulate press event in react-native
      • Shallow with New React Context API. Consumer not getting context
      • Enzyme is not finding component by props
    • jest

      • TypeError: Cannot read property 'Object.' of null
      • Jest - how to test if a component does not exist?
      • Refs not working in component being shallow rendered
      • ReferenceError: You are trying to import a file after the Jest environment has been torn down.
    • react-native

      • requiring image in react-native
      • Cannot find module 'setupDevtools' from 'setup.js'
      • Unable to resolve module ./views/assets/back-icon.png

    起源地下载网 » 使用 Jest 和 Enzyme 进行 React Native 单元测试|技术点评

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元