搭建 Jest+ Enzyme 測試環境

1.爲何要使用單元測試工具?

由於代碼之間的相互調用關係,又但願測試過程單元相互獨立,又能正常運行,這就須要咱們對被測函數的依賴函數和環境進行mock,在測試數據輸入、測試執行和測試結果檢查方面存在不少類似性,測試工具正是爲咱們在這些方面提供了方便。css

所謂單元測試也就是對每一個單元進行測試,通俗的將通常針對的是函數,類或單個組件,不涉及系統和集成。單元測試是軟件測試的基礎測試。html

2.React 的標配測試工具 Jest。

Jest主要有如下特色:node

      1.適應性:Jest是模塊化、可擴展和可配置的。react

     2.沙箱和快速:Jest虛擬化了JavaScript的環境,能模擬瀏覽器,而且並行執行webpack

     3.快照測試:Jest可以對React 樹進行快照或別的序列化數值快速編寫測試,提供快速更新的用戶體驗。web

     4.支持異步代碼測試:支持promises和async/awaitnpm

     5.自動生成靜態分析結果:不只顯示測試用例執行結果,也顯示語句、分支、函數等覆蓋率。json

JEST對比Mocha來講,由於以下幾個優勢最後勝出:redux

     1.和React師出同門,FB官方支持api

     2.已經集成了測試覆蓋率檢查、mock等功能,不須要安裝額外的庫

     3.文檔完備,官方提供了和babel、webpack集成狀況下以及異步調用的測試解決方案

     4.官方提供snapshot testing解決方案

3.Jest + Enzyme的使用過程

1.安裝

$ nam install —save-dev jest

若是須要在測試項目中使用babel,須要安裝babel-jest

$ nam install —save-dev babel-jest

而後安裝enzyme

$ npm install enzyme —save-dev

若是使用的react版本在13以上,還須要安裝react-addons-test-utils

$ nam i —save-dev react-addons-test-utils

 

2.配置

JEST運行基礎功能雖然無需配置,可是官方依然提供了配置選項來實現個性化需求。

package.json文件中配置jest的collectCoverageFrom參數,來指定檢查全部須要測試的文件(不管源文件有沒有被測試文件使用到)

coverageThreshold 參數來配置測試覆蓋率。

 

jest.config.js 配置jest:

module.exports = {

  bail: true, //趕上 test feature, 則Stop running test, 默認值是false

  cacheDirectory: './node_modules/.cache', //測試緩存數據的存儲位置

  testEnvironment: 'jsdom', //default brower-like enviroment, 若是你搭建了一個node service node-like enviroment

  coverageThreshold: { //測試覆蓋率, 閾值不知足,就返回測試失敗

    global: {

      branches: 90,

      functions: 90,

      lines: 90,

      statements: 90,

    },

  },

  coveragePathIgnorePatterns: [ //該路徑下的測試,忽略在測試覆蓋率上

    'build',

    '<rootDir>/src/shared/libs/url/',

  ],

  testRegex: 'test/.*\\.jsx?$', //要測試的文件目錄及後綴

  testPathIgnorePatterns: [ //忽略該路徑的文件測試

    '<rootDir>/node_modules/',

    '<rootDir>/build/',

    '<rootDir>/scripts/',

    '<rootDir>/api/',

    '<rootDir>/test/setup.js',

    '__mocks__',

  ],

  moduleFileExtensions: ['', 'json', 'js', 'jsx', 'less'], //測試模塊中用到的文件的後綴名配置

  modulePaths: ['<rootDir>/src', '<rootDir>'],

  moduleNameMapper: {  //與測試無關的資源文件贊成mock 掉,這樣在import 的時候就不會真的引入這些文件

    '^import?': '<rootDir>/build/jestImportMock.js',

    '\\.(css|less|gif|jpg|jpeg|png)$': '<rootDir>/build/jestStyleMock.js',

  },

  setupFiles: ['<rootDir>/test/setup.js'], //給每一個測試文件添加額外的配置

  transformIgnorePatterns: [ //測試過程不改變知足配置的文件

    '<rootDir>/node_modules/(?!(react-aaui|tempest\\.js)/)',

    'babel-runtime',

  ],

}

 

4.瞭解React官方測試工具庫

react測試能夠分爲測試DOM結構 和測試Action和Reducer

React官方測試工具庫提供兩種測試形式:

1.Shallow Rendering 測試虛擬DOM的方法 

Shallow Rendering (淺渲染)指的是,將一個組件渲染成虛擬DOM對象,可是隻渲染第一層,不渲染全部子組件,因此處理速度很是快。它不須要DOM環境,由於根本沒有加載進DOM。

import TestUtils from 'react-addons-test-utils';

function shallowRender(Component) {

  const renderer = TestUtils.createRenderer();

  renderer.render(<Component/>);

  return renderer.getRenderOutput();

}

Shallow Rendering 函數,該函數返回的就是一個淺渲染的虛擬DOM對象。只有一層,不返回子組件。

2.DOM Rendering 測試真實DOM的方法

官方測試工具庫的第二種測試方法,是將組件渲染成真實的DOM節點,再進行測試。這時就須要調用renderIntoDocument 方法。

import TestUtils from 'react-addons-test-utils';

import App from '../app/components/App';

const app = TestUtils.renderIntoDocument(<App/>);

renderIntoDocument 方法要求存在一個真實的DOM環境,不然會報錯。所以,測試用例之中,DOM環境(即window, document 和 navigator 對象)必須是存在的

 

Enzyme庫對官方測試庫進行了封裝,它提供三種方法:

import { shallow, mount, render } from ‘enzyme’

shallow 返回組件的淺渲染,對官方shallow rendering 進行封裝

const wrapper = shallow(<Counter {...props} />)

expect(wrapper.find('button').exists()).toBeTruthy()

shallow 返回Counter 的淺渲染,而後調用find 方法查找 button 元素

關於find方法,有一個注意點,就是它只支持簡單選擇器,稍微複雜的一點的CSS選擇器都不支持。

render 方法將React組件渲染成靜態的HTML字符串,而後分析這段HTML代碼的結構,返回一個對象。它跟shallow方法很是像,主要的不一樣是採用了第三方HTML解析庫Cheerio,它返回的是一個Cheerio實例對象。

const wrapper = render(<Counter {...props} />)

expect(wrapper.find('button').exists()).toBeTruthy()

render方法與shallow方法的API基本是一致的。

Enzyme的設計就是,讓不一樣的底層處理引擎,都具備一樣的API

 

mount 方法用於將React組件加載爲真實DOM節點。

const wrapper = mount(<Counter arithmetic={arithmetic})

 wrapper.find('button').simulate('click')

 

Enzyme的一部分API,你能夠從中瞭解它的大概用法。

.get(index):返回指定位置的子組件的DOM節點

.at(index):返回指定位置的子組件

.first():返回第一個子組件

.last():返回最後一個子組件

.type():返回當前組件的類型

.text():返回當前組件的文本內容

.html():返回當前組件的HTML代碼形式

.props():返回根組件的全部屬性

.prop(key):返回根組件的指定屬性

.state([key]):返回根組件的狀態

.setState(nextState):設置根組件的狀態

.setProps(nextProps):設置根組件的屬性

 

toMatchSnapshot方法會去幫你對比此次將要生成的結構與上次的區別

 

測試 異步action

他的I/O可能依賴store.getState(),自身又會依賴異步中間件,這類使用原生js測試起來比較困難,咱們的目的能夠設定爲:當咱們觸發一個action後,它經歷了一個圈異步最終store.getAction中的action拿到的數據和咱們預期一致。所以咱們須要用到兩個庫:redux-mock-store 和 nock。

const mockStore = configureStore([thunk, promiseMiddleware]) //配置mock 的store,讓他們有相同的middleware

afterEach(() => 

 nock.cleanAll()

)  //每執行完一個測試後,清空nock

 

const store = mockStore({

   router: {

      location: '/',

    },

})  //以咱們約定的初始state建立store,控制 I/O 依賴

 

const data = [

//接口返回的信息

{…

},

]

 

nock(API_HOST) //攔截請求返回的response

.get(`/api/…`) //拼接路由,須要在test.js中配置測試路徑

.reply(200, {code: 0, data})

return store.dispatch(actions.getAll()).then(() => expect(store.getActions()).toMatchSnapShot())

 

1.用nock來mock攔截http請求結果,並返回咱們給定的response

2.用redux-mock-store 來mock store 的生命週期,須要預先把middleware配成和項目一致

3.describe會包含一些生命週期的api,好比所有測試開始作啥,單個測試結束作啥api,這裏每執行完一個測試就清空nock

4.用了jest中的toMatchSnapShot api 來判斷兩個條件是否一致。

原本你要寫成 expect(store.getActions()).toEqual({data …}) 你須要把equal 裏的東西都描寫具體,而toMatchSnapshot

可在當前目錄下生成一個snapshot ,專門存放當前結果,寫測試時看一眼是預期的就commit。若是改壞了,函數就不匹配snapshot了。

5.測試注意事項

1.拆分單元,關注輸入輸出,忽略中間過程。dom測試時只用確保正確調用了action函數,傳參正確,而不用關注函數調用結果,置於action處理結果,reducer中對state的改變這些都留給action和reducer本身的單元測試區測。不要想着測試整個大功能的流程,不要有閉環的思想,單元測試須要保證的當前單元正常,對於每一個單元模塊輸入輸出都正確,理論串聯後一塊兒使用閉環時也會正確。

 

2.多種狀況的測試覆蓋,若是不能保證測試的全面性,每種狀況都覆蓋到,那麼這個測試就是個不敢依靠的不全面的測試。固然在實際項目中,可能由於時間、資源等問題,沒法保證每種狀況都測試到,而只測試主要的內容,這時候要作到內心有數,反正我是對於每一個測試都寫註釋的,交代清楚測試覆蓋了哪些,還有哪些沒有覆蓋,須要其餘手段保持穩定性。

 

3.關注該關注的,可有可無的mock掉。css、圖片這種mock掉,http請求mock掉

 

4.本來不利於測試的代碼仍是須要修改的,並不能爲了原代碼穩定不變,在測試時不敢動原代碼。譬如函數不純,沒有返回值等。

相關文章
相關標籤/搜索