React單元測試實戰

  • 蘇格團隊
  • 做者:Dee

前言

單元測試的好處css

  • 可保證獲得結果的一致性,提升項目、組件穩定性。
  • 開發者按單元測試思路去寫代碼,可清晰代碼結構,提升代碼的可讀性。

因爲筆者開發的項目愈來愈大,公共組件的複用性高,故其穩定性尤其重要。所以,引入單元測試刻不容緩。html

單元測試的很差node

  • 會佔用必定的開發成本,增長開發工做量。
  • 舊項目加入單元測試改動很大,會有必定的風險。
  • 會有必定的學習成本,對開發者要求比較高。
  • 若是在一些複用性很低的組件使用單元測試,成效不大且開發成本高。

選型

在作項目單元測試前,筆者參考了網上的一些文章以及官方文檔,最後選型爲Jest + react-test-renderer + Enzyme。react

  • Jestgit

    Jest 是 Facebook 出品的一個測試框架,相對其餘測試框架,其一大特色就是就是集成了 Mocha,chai,jsdom,sinon等功能,內置了經常使用的測試工具,好比自帶斷言、測試覆蓋率工具,實現了開箱即用。github

  • react-test-renderweb

    配合react-test-render,Jest 可提供了快照測試功能。npm

    首次運行快照測試,會產生一個可讀的快照,再次測試時會經過比對快照文件和新產生的快照判斷測試是否經過。json

    Jest在執行的時候若是發現toMatchSnapshot方法,會在同級目錄下生成一個__ snapshots__文件夾用來存放快照文件,之後每次測試的時候都會和第一次生成的快照進行比較。瀏覽器

  • Enzyme

    React官方已經提供了一個測試工具庫:react-dom/test-utils。可是用起來不夠方便,因而有了一些第三方的封裝庫,好比Airbnb公司的Enzyme。其兩大特色:

    • 提供了一套簡潔強大的 API,並內置Cheerio
    • 實現了jQuery風格的方式進行DOM 處理,開發體驗十分友好

    三種渲染方法

    shallow:淺渲染,是對官方的Shallow Renderer的封裝。將組件渲染成虛擬DOM對象,只會渲染第一層,子組件將不會被渲染出來,使得效率很是高。不須要DOM環境, 並可使用jQuery的方式訪問組件的信息

    render:靜態渲染,它將React組件渲染成靜態的HTML字符串,而後使用Cheerio這個庫解析這段字符串,並返回一個Cheerio的實例對象,能夠用來分析組件的html結構

    mount:徹底渲染,它將組件渲染加載成一個真實的DOM節點,用來測試DOM API的交互和組件的生命週期。用到了jsdom來模擬瀏覽器環境

    三種方法中,shallowmount由於返回的是DOM對象,能夠用simulate進行交互模擬,而render方法不能夠。通常shallow方法就能夠知足需求,若是須要對子組件進行判斷,須要使用render,若是須要測試組件的生命週期,須要使用mount方法。

    注意:enzyme還須要根據React的版本安裝適配器,適配器對應表以下:

方案

前面說了這麼多,是時候上代碼了。

  • 目錄

    筆者在根目錄新建一個unitTest目錄,其目錄結構爲:

    • jest.config.js:jest配置文件

    • mocks:mock文件目錄

    • components:項目的公共組件單元測試用例目錄

    • components/__ snapshots __:運行單元測試時自動生成的快照存放目錄

  • 安裝(因爲筆者是react16版本,因此安裝的適配器版本爲enzyme-adapter-react-16)

    npm install jest enzyme enzyme-adapter-react-16 react-test-renderer

  • 配置

    Jest支持直接在package.json文件寫入配置,但筆者有輕微潔癖,喜歡把配置文件寫到unitTest裏面,方便查找以及閱讀。

    // package.json
    {
        "scripts": {
            "jest": "jest --config ./unitTest/jest.config.js", // 單元測試
            "jestupdate": "jest --config ./unitTest/jest.config.js --updateSnapshot" // 單元測試快照更新
            "jestreport": "jest --config ./unitTest/jest.config.js --coverage" // 單元測試並生成覆蓋率報告
        }
    }
    複製代碼
    // jest.config.js
    module.exports = {
        testURL: 'http://localhost/',
        setupFiles: [],
        moduleFileExtensions: ['js', 'jsx'],
        testPathIgnorePatterns: ['/node_modules/'],
        testRegex: '.*\\.test\\.js$',
        collectCoverage: false,
        collectCoverageFrom: ['src/components/**/*.{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|scss)$': '<rootDir>/mocks/styleMock.js'
        }
    };
    
    複製代碼
    • testURL: jsdom運行url,默認爲"about:blank",若是不設置,會在嘗試訪問localStorage出錯。
    • setupFiles:運行測試代碼前,Jest會先運行setupFile指定的配置文件來初始化測試環境。
    • moduleFileExtensions:支持單元測試的文件擴展名。
    • testPathIgnorePatterns:匹配忽略文件規則。
    • testRegex:匹配測試文件規則。
    • collectCoverage:是否生成測試覆蓋報告,開啓會增長測試時間。
    • collectCoverageFrom:指示應收集覆蓋率信息的一組文件。若是文件與指定的glob模式匹配,即便此文件不存在測試,也將爲其收集覆蓋率信息,而且測試套件中從不須要它。
    • moduleNameMapper:可用於將模塊路徑映射到不一樣的模塊。默認狀況下,預設將全部圖像映射到圖像存根模塊,但若是找不到模塊,可配置此選項。
  • mock文件

    // fileMock.js
    module.exports = {};
    複製代碼
    // styleMock.js
    module.exports = {};
    複製代碼
  • 編寫單元測試

    // button.test.js
    import Button from '../../src/common/components/Button';
    import renderer from 'react-test-renderer';
    import React from 'react';
    import { shallow, configure } from 'enzyme'; // shallow(淺渲染,只渲染父組件)
    import Adapter from 'enzyme-adapter-react-16'; // 適應React-16
    configure({ adapter: new Adapter() }); // 適應React-16,初始化
    const props = {
        text: '按鈕測試用例',
        type: 'white',
        style: { marginTop: 15 },
        size: 'big',
        disabled: false,
        height: 'middle',
        isLock: true,
        cname: 'hello',
        onClick: () => {}
    };
    describe('test Button', () => {
        it('button render correctly', () => {
            const tree = renderer.create(<Button {...props} />).toJSON();// 生成快照
            expect(tree).toMatchSnapshot(); // 匹配以前的快照
        });
    
        it('button has class', () => {
            const item = shallow(<Button {...props} />); //淺渲染
            expect(item.hasClass('hello')).toBe(true); // 斷言有item有hello的className
        });
    });
    
    
    複製代碼

後記

注意事項:

一、若是不配置testURL,會報錯:localStorage is not available for opaque origins

二、本文檔只講述筆者的實踐方案以供參考,關於Jest、enzyme的具體介紹、用法可參考

相關文章
相關標籤/搜索