搭建 Jest+ Enzyme 測試環境


1.為什么要使用單元測試工具?

因為代碼之間的相互調用關系,又希望測試過程單元相互獨立,又能正常運行,這就需要我們對被測函數的依賴函數和環境進行mock,在測試數據輸入、測試執行和測試結果檢查方面存在很多相似性,測試工具正是為我們在這些方面提供了方便。

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

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

Jest主要有以下特點:

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

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

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

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

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

JEST對比Mocha來說,因為如下幾個優點最后勝出:

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

     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.原本不利於測試的代碼還是需要修改的,並不能為了原代碼穩定不變,在測試時不敢動原代碼。譬如函數不純,沒有返回值等。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM