之前单元测试在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, 是一个不错的选择。最后一句有那么一点点养分的话
当写测试代码很麻烦的时候,使用测试替身,绝对是不二选择