设置
使用 Mocha 作为测试引擎
注意因为是在 node 环境下运行, 所以不能访问 DOM
npm install --save-dev mocha
`</pre>
若想要结合 Babel 使用, 需要在 package.json 的 script 中加入一段
<pre>`{
"scripts": {
...
"test": "mocha --compilers js:babel/register --recursive",
"test:watch": "npm test -- --watch"
}
}
`</pre>
### Action Creators
Redux 里的 action creators 会返回普通对象, 在测试 action creators 的时候我们要测试的不仅是调用了正确的action creators, 还有是否返回了正确的 action
实例
<pre>`export function addTodo(text){
return {
type: 'ADD_TODO',
text
};
}
`</pre>
可以这样测试
<pre>`import expect form 'expect'
import * as actions from '../../actions/TodoActions';
import * as types from '../../constants/ActionTypes';
describe('actions', () => {
it('should create an action to add a todo', () => {
const text = 'Finish docs';
const expectedAction = {
type: 'ADD_TODO',
text
};
expect(actions.addTodo(text)).toEqual('expectedAction');
})
})
`</pre>
### 异步 Action Creators
对于使用Redux Thunk 或其他 middleware 的异步 Action Creator, 最好完全模拟 Redux Store 来测试.
可以使用 applyMiddleware() 和一个 mock store, 与可以用 nock 来模拟 HTTP 请求
实例:
<pre>`function fetchTodosRequest () {
return {
type: FETCH_TODOS_REQUEST
}
}
function fetchTodosSuccess(body) {
return {
type: FETCH_TODOS_SUCCESS,
body
}
}
function fetchTodosFailure(ex) {
return {
type: FETCH_TODOS_FAILURE,
ex
}
}
export function fetchTodo () {
return dispatch => {
dispatch(fetchTodosRequest())
return fetch('http://example.com/todos')
.then(res => res.json())
.then(json => dispatch(fetchTodosSuccess(json.body)))
.catch(ex => dispatch(fetchTodosFailure(ex)))
}
}
`</pre>
可以这样测试
<pre>`import expect from 'expect'
import { applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import * as actions from '../../actions/counter'
import * as types from '../../constants/ActionTypes'
import nock from 'nock'
const middleware = [thunk]
/**
* 使用中间件模拟 Redux Store
*/
function mockStore = (getState, expectedActions, done){
if(!Array.isArray(expectedActions)){
throw new Error('expectedActions should be an array of expected actions.')
}
if( typeof done !== 'undefined' && typeof done !== 'function'){
throw new Error('done should either be undefined or function')
}
function mockStoreWithoutmiddleware(){
return {
getState(){
return typeof getState === 'function' ?
getState():
getState
},
dispatch(action){
const expectedAction = expectedActions.shift()
try {
expect(action).toEqual(expectedAction)
if(done&&!expectedActions.length){
done()
}
return action
} catch (e) {
done(e)
}
}
}
}
const mockStoreWithMiddleware = applyMiddleware(
...middlewares
)(mockStoreWithoutMiddleware)
return mockStoreWithMiddleware()
}
describe('async actions', () => {
afterEach(() => {
nock.clearAll()
})
it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', (done) => {
nock('http://example.com')
.get('./todos')
.reply(200, {todos: ['do something']})
const expectedActions = [
{type: types.FETCH_TODOS_REQUEST},
{type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something']}}
]
const store = mockStore({todos: []}, expectedActions, done)
store.dispatch(actions.fetchTodos())
})
})
`</pre>
### Reducers 测试
Reducer 把 action 应用到当前 state, 并返回新的 state
<pre>`import { ADD_TODO } from '../constants/ActionTypes';
const initialState = [
{
text: 'Use Redux',
completed: false,
id: 0
}
]
export default function todos(state = initialState, action) {
switch(action.type) {
case ADD_TODO: return [
{
id: state.reduce((maxID, todo) => Math.max(todo.id, maxId), -1) + 1,
completed: false,
text: action.text
},
...state
]
default: return state
}
}
`</pre>
可以这样测试:
<pre>`import expect from 'expect'
import reducer from '../../reducers/todos'
import * as types from '../../constants/ActionTypes'
describe('todos reducer', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {})).toEqual([
{
text: 'Use Redux',
completed: false,
id: 0
}
])
})
it('should handle ADD_TODO', () => {
expect(
reducer([],{
type: types.ADD_TODO,
text: 'Run the test'
})
).toEqual(
[
{
text: 'Run the test',
completed: false,
id: 0
}
]
)
expect(
reducer([
{
text: 'Use Redux',
completed: false,
id:0
}
], {
type: types.ADD_TODO,
text: 'Run the test'
})
).toEqual([
{
text: 'Run the test',
completed: false,
id: 1
},
{
text: 'Use Redux',
completed: false,
id: 0
}
])
})
})
`</pre>
### 测试 Components
React Components 的优点是, 一般都很小, 并且依赖于 props, 因此测试起来很容易.
首先安装 React Test Utilities
<pre>`npm install --save-dev react-addons-test-utils
`</pre>
要测试 components 我们要创建一个叫`setup()` 的辅助方法, 用来把模拟过的( stubbed ) 回调函数当做 props 传入, 然后使用 React 浅渲染来渲染组建, 这样可以依据`是否调用了回调函数`的断言来写独立的测试
<pre>`import React, { PropTypes, Component } from 'react'
import TodoTextInput from './TodoTextInput'
class Header extends Component {
handleSave(text){
if(text.length !==0){
this.props.addTodo(text)
}
}
render(){
return(
<header>
<h1>todos</h1>
<TodoTextInput newTodo = {true} onSave ={this.handleSave.bind(this)} placeholder = 'what needs to be done?' />
</header>
)
}
}
Header.propTypes = {
addTodo: PropTypes.func.isRequired
}
export default Header