由於代碼之間的相互調用關係,又但願測試過程單元相互獨立,又能正常運行,這就須要咱們對被測函數的依賴函數和環境進行mock,在測試數據輸入、測試執行和測試結果檢查方面存在不少類似性,測試工具正是爲咱們在這些方面提供了方便。css
所謂單元測試也就是對每一個單元進行測試,通俗的將通常針對的是函數,類或單個組件,不涉及系統和集成。單元測試是軟件測試的基礎測試。html
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解決方案
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',
],
}
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了。
1.拆分單元,關注輸入輸出,忽略中間過程。dom測試時只用確保正確調用了action函數,傳參正確,而不用關注函數調用結果,置於action處理結果,reducer中對state的改變這些都留給action和reducer本身的單元測試區測。不要想着測試整個大功能的流程,不要有閉環的思想,單元測試須要保證的當前單元正常,對於每一個單元模塊輸入輸出都正確,理論串聯後一塊兒使用閉環時也會正確。
2.多種狀況的測試覆蓋,若是不能保證測試的全面性,每種狀況都覆蓋到,那麼這個測試就是個不敢依靠的不全面的測試。固然在實際項目中,可能由於時間、資源等問題,沒法保證每種狀況都測試到,而只測試主要的內容,這時候要作到內心有數,反正我是對於每一個測試都寫註釋的,交代清楚測試覆蓋了哪些,還有哪些沒有覆蓋,須要其餘手段保持穩定性。
3.關注該關注的,可有可無的mock掉。css、圖片這種mock掉,http請求mock掉
4.本來不利於測試的代碼仍是須要修改的,並不能爲了原代碼穩定不變,在測試時不敢動原代碼。譬如函數不純,沒有返回值等。