Jest基於dva框架的單元測試最佳實踐

前言

之前單元測試在JavaScript項目中配置其實仍是挺繁瑣的,依賴各類庫mocha,chai,sion或者第三方覆蓋率報表生成庫,可是如今Facebook推出了Jest測試框架,並在react native項目初始化時就已經集成了該環境,因此還沒玩過的同窗們能夠耐心的看下去,說不定玩一次就愛上了寫單元測試呢。javascript

Jest框架

Jest已經內置了斷言,mock方案,以及異步處理(async/await),只需簡單配置便可導出代碼覆蓋率報告,還有針對於UI的快照測試。官方聲稱的Delightful JavaScript Testing 😁java

環境配置

由於是基於dva框架開發的react native項目,因此咱們着重測試model類的方法(reducers和effects)node

  • package.json中針對jest的配置
"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掉。怎麼作能?

  • 方法一:能夠直接在當前測試文件,在import前執行以下代碼:
jest.mock('react-native', () => {
    NativeModule: {
        YCUserInfoPlugin: {
            setUserToken: () => {}
        }
    }
})

import ...
import ...

code
複製代碼
  • 方法二:在建立一個名爲mocks的文件夾,由於須要mock的react-native包中NativeModule對象中的YCUserInfoPlugin,因此建立建立文件爲react-native.js,而後在package.json的moduleNameMapper中配置改文件的路徑,即 包名: '文件所在的路徑'

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介紹

當你在寫測試代碼中不順利的時候,或是把其中的代碼變爲測試替身,絕對是一個不二選擇。下面能夠看下簡單的測試用例,來了解下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()
  })
})

複製代碼

從上面的針對spystubmock的測試用例能夠很明顯的看出,spy見名知義,主要是在不改變函數自己的前提下,收集函數自己的信息,如:是否被調用,調用的參數等等。


stub主要將一些有不肯定因素的函數替換掉,保證返回的結果是你想要的,好比而後根據不一樣的返回值來覆蓋不一樣的語句,基本上網絡請求呀,數據庫呀還有一些耗時操做等.


mock這個詞就頗有爭議啦,當你纔開始寫單元測試的時候,遇到一個函數中的操做很差寫測試的時候,有的前輩可能就會說把它mock掉啊,而後你就去google,可是可能最後你就只是stub那個對象或是函數,就造成了不少人對mock和stub有點傻傻分不清的,我就是其中一個,啊哈哈哈哈哈。其實mock來講應該謹慎使用,由於mock可能會使對象變得很具體,具體就表明着不靈活了,對於測試用例來講這是很致命的,適用性大大下降。mock出來的對象最大的特色就是它自帶斷言,並且不會真正的走測試代碼邏輯,而後咱們在代碼執行後,驗證該邏輯是不是咱們想要的。

有些話想要講

相對入門級測試玩家來講Jest絕對是一大福音,環境配置簡單,直接能夠上手。固然,當你寫的測試代碼越多,你可能想要測試得更細粒度,更全面,再上手Sinon, 是一個不錯的選擇。最後一句有那麼一點點養分的話

當寫測試代碼很麻煩的時候,使用測試替身,絕對是不二選擇

相關文章
相關標籤/搜索