之前單元測試在JavaScript項目中配置其實仍是挺繁瑣的,依賴各類庫mocha,chai,sion或者第三方覆蓋率報表生成庫,可是如今Facebook推出了Jest測試框架,並在react native項目初始化時就已經集成了該環境,因此還沒玩過的同窗們能夠耐心的看下去,說不定玩一次就愛上了寫單元測試呢。javascript
Jest已經內置了斷言,mock方案,以及異步處理(async/await),只需簡單配置便可導出代碼覆蓋率報告,還有針對於UI的快照測試。官方聲稱的
Delightful JavaScript Testing
😁java
由於是基於dva框架開發的react native項目,因此咱們着重測試model類的方法(reducers和effects)node
"jest": {
"preset": "react-native",
"collectCoverage": true,
"coverageReporters": [
"lcov"
],
"transformIgnorePatterns": [
"node_modules/(?!react-native|react-navigation)"
],
"moduleNameMapper": {
"react-native": "<rootDir>/mocks/react-native.js"
}
複製代碼
collectCoverage
是否開啓跑測試代碼時收集覆蓋率react
coverageReporters
導出報告文件類型(經過該導出的文件和上傳到sonar分析) transformIgnorePatternsgit
transformIgnorePatterns
將一些model中涉及到的npm進行babel轉換,否則在測試中沒法識別es6的語法es6
moduleNameMapper
指定須要mock庫對應的mock文件github
首先,介紹下這個model的reducr和effect方法的功能(具體dva的model怎麼寫,能夠github下,這裏很少篇幅講解)。reducers中的changeLoginStatus很簡單就是根據payload的對象改變state中對應的key;而effects中的login方法(注:這是一個generator)就是根據請求體payload中的參數進行網絡請求,這裏我已經封裝成一個方法了,根據返回的response來調用對應的action,從而改變state。數據庫
login.jsnpm
import { NativeModules} from 'react-native'
import { NavigationActions } from '../../utils'
import quickLogin from '../../utils/userAccount'
import Toast from '../../utils/Toast'
import {fetchisCompletedUserInfo} from '../fill-information/server'
import {
fetchUserInfoAndUpdateLocal
} from '../user-info/server'
const {
YCUserInfoPlugin,
} = NativeModules
const accountInfo = {
phoneNum: 18581111111,
code: 11111
}
export default {
namespace: 'login',
state: {
isLogin: false,
failReason: null
},
reducers: {
changeLoginStatus(state, {payload}) {
return {
...state,
isLogin: payload.isLogin,
failReason: payload.failReason
}
}
},
effects: {
* login({payload}, { call, put }) {
try {
const res = yield call(quickLogin, payload.phoneNum, payload.code)
if (res.succeed) {
yield call(YCUserInfoPlugin.setUserToken, res.data)
yield put({ type: 'changeLoginStatus', payload: {
isLogin: true
}})
} else {
yield put({ type: 'changeLoginStatus', payload: {
isLogin: false,
failReason: 'test-failReason'
}})
}
} catch (error) {
global.log(error)
}
}
}
}
複製代碼
主要就是測試reducer和effect方法json
login-test.js
describe('LoginModel------------>reducer', () => {
it('changeLoginStatus -> state all key should change to setvalue', () => {
// reduce 參數1:state初始值;參數2:action
expect(reduces.notifyVerificatioStatus(
{...payload},
{type: 'changeLoginStatus', payload: {
isLogin: false,
failReason: 'test-failReason'
}}
)).toEqual({...payload, isLogin: false, failReason: 'test-failReason'})
})
})
describe('LoginModel------------>effects', () => {
it('login -> login success with phone number', () => {
// Given
const {call, put} = effects
const saga = quickLogin.effects.login
const actionCreator = {
type: 'login',
payload: {
...accountInfo
}
}
// When
const generator = saga(actionCreator, {call, put})
generator.next()
generator.next({
succeed: true,
data: 'Test-User-Token'
})
const changeLoginStatus = generator.next()
const end = generator.next()
// Then
expect(changeLoginStatus.value).toEqual(put({
type: 'changeLoginStatus',
payload: {
isLogin: true
}}))
expect(end.done).toEqual(true)
})
})
複製代碼
其中yield call(YCUserInfoPlugin.setUserToken, res.data)
這是調用一個NativeModule方法,在執行測試的時候,你可能會發現會報找不到YCUserInfoPlugin的setUserToken方法,各位看官不急,由於這個是寫在native的,咱們也不須要關係它是否正確,只需知道調用了這句話便可,咱們能夠把它mock掉。怎麼作能?
jest.mock('react-native', () => {
NativeModule: {
YCUserInfoPlugin: {
setUserToken: () => {}
}
}
})
import ...
import ...
code
複製代碼
包名: '文件所在的路徑'
mocks/react-native.js
export default const NativeModules = {
YCUserInfoPlugin: {
setUserToken: () => {}
}
}
複製代碼
這樣jest就知道在跑測試代碼時,去找咱們mock的文件了,test case 也能夠順利跑過了。由於這個測試用例中只須要知道那句代碼執行就ok啦。
在執行單個測試用例的時候,有可能會遇到全局設置的問題,你能夠在beforeAll()或是在afterAll()週期方法中作一些初始化和回滾現場的操做。 通常來講咱們主要測試數據交互的模塊,因此model就是重點,正常來講咱們網絡請求這塊是須要mock掉的,可是由於在dva框架中,咱們通常把網絡請求封裝在effects中,並且這個方法是個generator函數(dva框架集成的redux-saga),咱們能夠很方面的在裏面的每個yeild語句裏自定義返回值,就能夠設置不一樣類型的返回值,來執行不一樣的語句覆蓋。
jest中針對於測試替身這塊的能力仍是沒有Sinon厲害,並且API又少,文檔有誤導 性,想要更深刻的寫一些測試用例還得藉助第三方的包。
當你在寫測試代碼中不順利的時候,或是把其中的代碼變爲測試替身,絕對是一個不二選擇。下面能夠看下簡單的測試用例,來了解下Sinon的幾大概念。
person.js
export default class Person {
static say(message) {
console.log('person say ', message)
}
static eat(food) {
return `person eat ${food}`
}
static save(name) {
console.log(`person saved -> ${name}`)
}
}
複製代碼
person-test.js
import Person from '../person'
import sinon from 'sinon'
describe('sinon test', () => {
it('spy', () => {
const message = 'hello world'
const spy = sinon.spy(Person, 'say')
Person.say(message)
expect(spy.withArgs(message).calledOnce).toEqual(true)
spy.restore()
})
it('stub', () => {
const message = 'hello world'
const returnValue = 'stub eat apple'
sinon.stub(Person, 'say').callsFake((message) => {
console.log(`stub log ${message}`)
})
const stub = sinon.stub(Person, 'eat')
stub.withArgs('apple').returns('stub eat apple')
const result = Person.eat('apple')
expect(stub).toEqual(returnValue)
stub.restore()
})
it('mock', () => {
const name = 'yellow'
const mock = sinon.mock(Person)
mock.expects('save').once().withArgs(name)
Person.save(name)
mock.verify()
mock.restore()
})
})
複製代碼
從上面的針對
spy
,stub
,mock
的測試用例能夠很明顯的看出,spy
見名知義,主要是在不改變函數自己的前提下,收集函數自己的信息,如:是否被調用,調用的參數等等。
stub
主要將一些有不肯定因素的函數替換掉,保證返回的結果是你想要的,好比而後根據不一樣的返回值來覆蓋不一樣的語句,基本上網絡請求呀,數據庫呀還有一些耗時操做等.
mock
這個詞就頗有爭議啦,當你纔開始寫單元測試的時候,遇到一個函數中的操做很差寫測試的時候,有的前輩可能就會說把它mock掉啊,而後你就去google,可是可能最後你就只是stub那個對象或是函數,就造成了不少人對mock和stub有點傻傻分不清的,我就是其中一個,啊哈哈哈哈哈。其實mock來講應該謹慎使用,由於mock可能會使對象變得很具體,具體就表明着不靈活了,對於測試用例來講這是很致命的,適用性大大下降。mock出來的對象最大的特色就是它自帶斷言,並且不會真正的走測試代碼邏輯,而後咱們在代碼執行後,驗證該邏輯是不是咱們想要的。
相對入門級測試玩家來講Jest絕對是一大福音,環境配置簡單,直接能夠上手。固然,當你寫的測試代碼越多,你可能想要測試得更細粒度,更全面,再上手Sinon, 是一個不錯的選擇。最後一句有那麼一點點養分的話
當寫測試代碼很麻煩的時候,使用測試替身,絕對是不二選擇