jest+enzyme测试react组件

作者: 邢泽川 | 来源:发表于2018-11-21 11:30 被阅读2次

    搭建和配置

    1.安装依赖

    npm install jest --save-dev
    npm install enzyme --save-dev
    npm install enzyme-to-json --save-dev  //为快照提供了json的组件格式
    

    2.package.json配置jest

    setupTestFrameworkScriptFile指定enzyme初始化文件;
    moduleNameMapper对css、less、图片等不影响JavaScript测试的静态文件进行mock。

    // package.json
      "jest": {
        "setupTestFrameworkScriptFile": "./setupTests.js",
        "moduleNameMapper": {
          "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
          "\\.(css|less)$": "<rootDir>/__mocks__/styleMock.js"
        }
      }
    

    3.enzyme初始化文件:setupTests.js

    新增setupTests.js如图:


    enzyme配合react16使用的初始化配置:

    // setupTests.js
    import Enzyme from "enzyme";
    import Adapter from "enzyme-adapter-react-16";
    
    Enzyme.configure({
      adapter: new Adapter()
    });
    

    4.mock文件夹

    新增mock文件夹;
    fileMock.js和styleMock.js分别对应package.json中jest的配置,用来模拟css、less和静态文件;
    list文件夹下是列表页的mock数据,配合enzyme对列表页进行测试;
    其他页面的mock数据可在此自行添加。

    5.gitignore

    新增忽略快照文件代码;
    在本地运行 npm run test 后可自动在test/snapshots下生成快照。

    // .gitignore
    __tests__/__snapshots__/
    

    6.安装VScode插件jest

    该插件可以方便我们不使用npm run test 也能即时看到测试结果;
    view snapshot按钮方便我们查看快照;
    debug按钮可以对测试代码进行调试。


    关于jest

    具体jest文档可参考https://jestjs.io/docs/en/api

    使用jest.fn()对方法进行mock

    import Component from "../component";
    const mock_fn = jest.fn();
    const wrapper = mount(
        < Component ></Component>
    );
    //使用enzyme的instance()方法将组件内的fn()方法替换为mock_fn()
    wrapper.instance().fn = mock_fn; 
    

    使用jest.spyOn()模拟跟踪某个类的方法的调用,如我们写的Mobx的store中的方法storeFn():

    import store from "../store";
    spy_storeFn = jest.spyOn(store, "storeFn");
    //使用reactWapper.instance()获取组件内部方法并进行mock
    category_mount.instance().clickTextToCenter = mock_clickTextToCenter;
    

    关于enzyme

    具体enzyme文档可参考https://airbnb.io/enzyme/

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

    mountshallow对组件的渲染结果不是html的dom树,而是react树,如果你chrome装了react devtool插件,他的渲染结果就是react devtool tab下查看的组件结构,而render的结果是element tab下查看的结果。这些只是渲染结果上的差别,更大的差别是shallowmount的渲染结果是个被封装的ReactWrapper,可以进行多种操作,譬如find()、parents()、children()等选择器进行元素查找;state()、props()进行数据查找,setState()、setprops()操作数据;simulate()模拟事件触发等。

    shallow只渲染当前组件,只能能对当前组件做断言;mount会渲染当前组件以及所有子组件,对所有子组件也可以做上述操作。一般交互测试都会关心到子组件,使用的都是mount。但是mount耗时更长,内存占用的更多,如果没必要操作和断言子组件,可以使用shallow

    文件引入(xxx.test.js)

    首先以简单的guide组件的测试为例:

    //list_guide.test.js
    import React from "react";  // 必须引入react
    import "../assets/configs/global_configs"; //引入全局的依赖文件以免npm run test时报错
    import { ns } from "../src/configs/configs"; //引入组件依赖的配置文件
    import store from "../src/pages/list/store";  //可以引入store,支持对store进行操作
    import mockList from "../__mocks__/list"; //引入mock数据
    import { shallow, render } from "enzyme";  //引入enzyme的渲染方法
    import Guide from "../src/pages/list/guide";  //引入待测的组件
    import toJson from "enzyme-to-json"; // 引入enzyme-to-json为快照提供了json的组件格式
    //import { BrowserRouter } from "react-router-dom"; 
    //对于使用<Route>包裹的组件需要进入BrowserRouter,
    //否则报错“You should not use <Route> or withRouter() outside a <Router>”
    
    describe("pages/list/guide",()=>{
      const { setValue } = store;
    
      it("should render without throwing an error",()=>{
        setValue("guide_visible", true);
        const Guide_render = render(
          <Guide store ={store}/> //直接使用store将引入的store传给待测组件
        );
        expect(toJson(Guide_render)).toMatchSnapshot();  //生成快照
    
        const Guide_shallow = shallow(
          <Guide store ={store}/>
        );
        expect(Guide_shallow.hasClass("hide")).toEqual(false);
        Guide_shallow.find(`.${ns}-guide`).at(0).simulate("click");
        expect(Guide_shallow.hasClass("hide")).toEqual(true);
      });
    });
    

    快照测试

    快照可以测试到组件的渲染结果是否与上一次生成的快照一致;
    toMatchSnapshot方法会帮助我们对比这次将要生成的结构与上次的区别;
    快照测试是最简单且收益很快的测试方法,建议每个组件都进行快照测试。

    // list_category.test.js
    import "../assets/configs/global_configs";
    import React from "react";
    import store from "../src/pages/list/store";
    import { shallow, mount, render } from "enzyme";
    import Category from "../src/pages/list/category";
    import { ns } from "../src/configs/configs";
    import { BrowserRouter } from "react-router-dom"; 
    import mockList from "../__mocks__/list"; 
    import toJson from "enzyme-to-json"; 
    
    describe("pages/list/category", () => {
      //执行每个用例之前清除掉所有mock
      beforeEach(()=>{
        jest.clearAllMocks();
      });
      const { setValue } = store;
      
      it("type = thirdparty_web", () => {
        setValue("category_data", mockList.category_data.thirdparty_web);
        //使用render进行快照测试,直接展示的是html树
        const category_render = render(
          <BrowserRouter>
            <Category store={store}></Category>
          </BrowserRouter>
        );
        
        //生成快照,如安装了VScode的jest插件,这里会显示view snapshot,点击可查看快照
        //toJson()将reactWrapper转化为json格式用来生成快照
        expect(toJson(category_render)).toMatchSnapshot();    
        
        //使用mount进行交互测试
        const category_mount = mount(
          <BrowserRouter>
            <Category store={store}></Category>
          </BrowserRouter>
        );
        expect(category_mount.find("Router span").text()).toEqual("益智游戏");
        expect(category_mount.find(".swiper-slide").at(1).text()).toEqual("推荐书籍");
      });
      
     ...
     
    });
    

    生成快照如图:


    交互测试

    主要利用enzyme的simulate()方法来模拟事件,通过触发事件绑定函数,模拟事件的触发。触发事件后,判断props上特定函数是否被调用,传参是否正确;组件状态是否发生预料之中的修改;store中的值是否按照预期变化;某个dom节点是否存在是否符合期望。

    // list_category.test.js
    import "../assets/configs/global_configs";
    import React from "react";
    import store from "../src/pages/list/store";
    import { shallow, mount, render } from "enzyme";
    import Category from "../src/pages/list/category";
    import { ns } from "../src/configs/configs";
    import { BrowserRouter } from "react-router-dom";
    import mockList from "../__mocks__/list";
    import toJson from "enzyme-to-json"; 
    
    describe("pages/list/category", () => {
      //执行每个用例之前清除掉所有mock
      beforeEach(()=>{
        jest.clearAllMocks();
      });
      const { setValue } = store;
      
      ...
    
      it("no dropdown", () => {
        setValue("category_data", mockList.category_data.no_dropdown);
        const category_render = render(
          < Category store={store}></Category>
        );
        expect(toJson(category_render)).toMatchSnapshot(); // 生成快照
        
        const category_mount = mount(
          < Category store={store}></Category>
        );
        expect(category_mount.find(`.${ns}-swiper-container .swiper-slide`).map(node => node.text()))
          .toEqual(["每月推荐", "中国影片", "欧洲电影", "亚洲电影", "国际影院", "儿童电影"]);
    
        const 
        //使用jest.fn()对方法进行mock
        mock_clickTextToCenter = jest.fn(),
        //使用jest.spyOn()模拟跟踪某个类的方法的调用
        spy_showList = jest.spyOn(store, "showList");
        //使用reactWapper.instance()获取组件内部方法并进行mock
        category_mount.instance().clickTextToCenter = mock_clickTextToCenter;
        //使用simulate()触发click事件
        category_mount.find(".swiper-slide").at(3).simulate("click");
        //检测模拟的Category组件内部的clickTextToCenter方法是否调用并且参数是3
        expect(mock_clickTextToCenter).toHaveBeenCalledWith(3);
        //检测store中的current_type是否已经变为good_page
        expect(store.current_type).toEqual("good_page");
          //检测store中的current_id是否已经变为30065936895
        expect(store.current_id).toEqual(30065936895);
        //检测模拟的store中的showList方法是否被调用并且参数是good_page,good_page,1
        expect(spy_showList).toHaveBeenCalledWith("good_page", good_page, 1);
      });
      
      ...
      
    });
    

    结语

    本文档还有很多不足之处,今后还会持续更新。
    希望大家今后在使用jest+enzyme进行测试时有任何好的测试思路与心得体会都分享一下,帮助我们一起积累react自动化测试的经验,使我们的项目更加健壮。

    参考链接

    https://jestjs.io/docs/en/api
    https://airbnb.io/enzyme/
    http://echizen.github.io/tech/2017/02-12-jest-enzyme-intro
    http://echizen.github.io/tech/2017/02-12-jest-enzyme-setup
    http://echizen.github.io/tech/2017/02-12-jest-enzyme-qa
    http://echizen.github.io/tech/2017/02-12-jest-enzyme-method
    http://echizen.github.io/tech/2017/04-28-jest-debug
    http://echizen.github.io/tech/2017/04-24-component-lifycycle-test

    相关文章

      网友评论

        本文标题:jest+enzyme测试react组件

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