Reudx 编写测试
Reudx 编写测试
由于你写的大部分 R 是,而且其中大部分是纯,所以很好测,不需要模拟。
准备工作
我们推荐使用 作为测试引擎,需要注意的是 Jest 运行在 Node 环境中,因此你不能访问 DOM。
npm install --save-dev jest@H__9@如果想要和 一起使用,还需要安装 babel-jest
npm install --save-dev babel-jest@H__9@并且在 .babelrc 中通过 来配置
{ "presets": ["@babel/preset-env"] }@H__9@然后,在 package.json 中的 scripts 处相关的命令
{ ... "scripts": { ... "test": "jest", "test:watch": "npm test -- --watch" }, ... }@H__9@执行 npm test 可以运行一次测试,执行 npm run test:watch 可以让每当改变时执行测试。
测试 Action Creators
在 R 中,action creators 是返回普通对象的,当我们测试 action creators 时,我们想要测试是否了正确的 action creator 以及是否返回了正确的 action。
示例
export function addTodo(text) { return { type: 'ADD_TODO', text } }@H__9@可以这样来测试:
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: types.ADD_TODO, text } expect(actions.addTodo(text)).toEqual(expectedAction) }) })@H__9@测试异步 Action Creators
对于使用 或者其它中间件的异步 action Creator ,最好完全模拟 R store 来进行测试,可以通过使用 来把中间件应用于模拟的 store,还可以使用 来模拟 HTTP 请求。
示例
import 'cross-fetch/polyfill' 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 fetchTodos() { return dispatch => { dispatch(fetchTodosRequest()) return fetch('http://example.com/todos') .then(res => res.json()) .then(body => dispatch(fetchTodosSuccess(body))) .catch(ex => dispatch(fetchTodosFailure(ex))) } }@H__9@可以这样来测试:
import conureMockStore from 'r-mock-store' import thunk from 'r-thunk' import * as actions from '../../actions/TodoActions' import * as types from '../../constants/ActionTypes' import fetchMock from 'fetch-mock' import expect from 'expect' // 可以使用任何测试库 const middlewares = [thunk] const mockStore = conureMockStore(middlewares) describe('async actions', () => { afterEach(() => { fetchMock.reset() fetchMock.restore() }) it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', () => { fetchMock .getOnce('/todos', { body: { todos: ['do something'] }, headers: { 'content-type': 'application/json' } }) const expectedActions = [ { type: types.FETCH_TODOS_REQUEST }, { type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } } ] const store = mockStore({ todos: [] }) return store.dispatch(actions.fetchTodos()).then(() => { // return of async actions expect(store.getActions()).toEqual(expectedActions) }) }) })@H__9@测试 Reducers
Reducer 把 action 应用到之前的 state,并返回新的 state。示例如下。
示例
import { ADD_TODO } from '../constants/ActionTypes' const initialState = [ { text: 'Use R', 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 } }@H__9@可以这样来测试:
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 R', completed: false, id: 0 } ]) }) it('should handle ADD_TODO', () => { expect( reducer([], { type: types.ADD_TODO, text: 'Run the tests' }) ).toEqual([ { text: 'Run the tests', completed: false, id: 0 } ]) expect( reducer( [ { text: 'Use R', completed: false, id: 0 } ], { type: types.ADD_TODO, text: 'Run the tests' } ) ).toEqual([ { text: 'Run the tests', completed: false, id: 1 }, { text: 'Use R', completed: false, id: 0 } ]) }) })@H__9@测试组件
React 组件有优点,它们通常很小且只依赖于传入的 props ,因此测试起来很简便。
首先,我们需要安装 ,Enzyme 底层使用了 ,但是更方便,更具可读性,更强大。
npm install —save-dev enzyme@H__9@为了兼容 React 的版本,我们还需要安装 Enzyme 适配器,Enzyme 提供了适配器用以兼容 React16 ,React 15.x,React 0.14.x,React 0.13.x。如果你使用的是 React16,你可以使用下面的命令安装相关依赖:
npm install --save-dev enzyme-adapter-react-16@H__9@为了测试组件,我们创建了 setup() 辅助,用来把模拟过的(stubbed)回调当作 props 传入,然后使用 () 来渲染组件。这样就可以依据 “是否了回调” 的断言来写独立的测试。
示例
import React, { Component } from 'react' import PropTypes from 'prop-types' import TodoTextInput from './TodoTextInput' class Header extends Component { handleSave(text) { if (text.length !== 0) { this.props.addTodo(text) } } render() { return ( <header className="header"> <h1>todos</h1> <TodoTextInput newTodo={true} onSave={this.handleSave.bind(this)} placeholder="What needs to be done?" /> </header> ) } } Header.propTypes = { addTodo: PropTypes.func.is } export default Header@H__9@上面的组件可以这样来测试:
import React from 'react' import Enzyme, { mount } from 'enzyme' import Adapter from 'enzyme-adapter-react-16'; import Header from '../../components/Header' Enzyme.conure({ adapter: new Adapter() }); function setup() { const props = { addTodo: jest.fn() } const enzymeWrapper = mount(<Header {...props} />) return { props, enzymeWrapper } } describe('components', () => { describe('Header', () => { it('should render self and subcomponents', () => { const { enzymeWrapper } = setup() expect(enzymeWrapper.find('header').hasClass('header')).toBe(true) expect(enzymeWrapper.find('h1').text()).toBe('todos') const todoInputProps = enzymeWrapper.find('TodoTextInput').props() expect(todoInputProps.newTodo).toBe(true) expect(todoInputProps.placeholder).toEqual('What needs to be done?') }) it('should call addTodo if length of text is greater than 0', () => { const { enzymeWrapper, props } = setup() const input = enzymeWrapper.find('TodoTextInput') input.props().onSave('') expect(props.addTodo.mock.calls.length).toBe(0) input.props().onSave('Use R') expect(props.addTodo.mock.calls.length).toBe(1) }) }) })@H__9@测试 connected 组件
如果你使用类似 的库,你可能会使用 ,比如 。可以让你把 R state 注入到常规的 React 组件中。
考虑如下 App 组件
import { connect } from 'react-r' class App extends Component { /* ... */ } export default connect(mapStateToProps)(App)@H__9@在单元测试中,通常会这样导入 App 组件:
import App from './App'@H__9@不过,上面这样导入的是通过 connect() 返回的包装组件,并非 App 组件本身,如果你想测试和 R 的整合,这很容易,通过 包裹它后传入用以单元测试的特殊 store 就可以了。但是有时候我们想测试的其实是不带 R store 的组件的渲染。
为了测试 App 组件本身而不用处理装饰器,我们推荐你导出未装饰的组件:
import { connect } from 'react-r' // 命名导出未连接的组件 (测试用) export class App extends Component { /* ... */ } // 认导出已连接的组件 (app 用) export default connect(mapStateToProps)(App)@H__9@由于认导出的组件依旧是包装过的组件,上面中的导入依旧会生效,无须你更改已有的。不过现在你可以通过下面这样的办法导入未装饰的组件了:
import { App } from './App'@H__9@如果你需要导入二者,可以按下面这样做:
import ConnectedApp, { App } from './App'@H__9@在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
如果想要和 一起使用,还需要安装 babel-jest
npm install --save-dev babel-jest@H__9@并且在 .babelrc 中通过 来配置
{ "presets": ["@babel/preset-env"] }@H__9@然后,在 package.json 中的 scripts 处相关的命令
{ ... "scripts": { ... "test": "jest", "test:watch": "npm test -- --watch" }, ... }@H__9@执行 npm test 可以运行一次测试,执行 npm run test:watch 可以让每当改变时执行测试。
测试 Action Creators
在 R 中,action creators 是返回普通对象的,当我们测试 action creators 时,我们想要测试是否了正确的 action creator 以及是否返回了正确的 action。
示例
export function addTodo(text) { return { type: 'ADD_TODO', text } }@H__9@可以这样来测试:
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: types.ADD_TODO, text } expect(actions.addTodo(text)).toEqual(expectedAction) }) })@H__9@测试异步 Action Creators
对于使用 或者其它中间件的异步 action Creator ,最好完全模拟 R store 来进行测试,可以通过使用 来把中间件应用于模拟的 store,还可以使用 来模拟 HTTP 请求。
示例
import 'cross-fetch/polyfill' 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 fetchTodos() { return dispatch => { dispatch(fetchTodosRequest()) return fetch('http://example.com/todos') .then(res => res.json()) .then(body => dispatch(fetchTodosSuccess(body))) .catch(ex => dispatch(fetchTodosFailure(ex))) } }@H__9@可以这样来测试:
import conureMockStore from 'r-mock-store' import thunk from 'r-thunk' import * as actions from '../../actions/TodoActions' import * as types from '../../constants/ActionTypes' import fetchMock from 'fetch-mock' import expect from 'expect' // 可以使用任何测试库 const middlewares = [thunk] const mockStore = conureMockStore(middlewares) describe('async actions', () => { afterEach(() => { fetchMock.reset() fetchMock.restore() }) it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', () => { fetchMock .getOnce('/todos', { body: { todos: ['do something'] }, headers: { 'content-type': 'application/json' } }) const expectedActions = [ { type: types.FETCH_TODOS_REQUEST }, { type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } } ] const store = mockStore({ todos: [] }) return store.dispatch(actions.fetchTodos()).then(() => { // return of async actions expect(store.getActions()).toEqual(expectedActions) }) }) })@H__9@测试 Reducers
Reducer 把 action 应用到之前的 state,并返回新的 state。示例如下。
示例
import { ADD_TODO } from '../constants/ActionTypes' const initialState = [ { text: 'Use R', 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 } }@H__9@可以这样来测试:
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 R', completed: false, id: 0 } ]) }) it('should handle ADD_TODO', () => { expect( reducer([], { type: types.ADD_TODO, text: 'Run the tests' }) ).toEqual([ { text: 'Run the tests', completed: false, id: 0 } ]) expect( reducer( [ { text: 'Use R', completed: false, id: 0 } ], { type: types.ADD_TODO, text: 'Run the tests' } ) ).toEqual([ { text: 'Run the tests', completed: false, id: 1 }, { text: 'Use R', completed: false, id: 0 } ]) }) })@H__9@测试组件
React 组件有优点,它们通常很小且只依赖于传入的 props ,因此测试起来很简便。
首先,我们需要安装 ,Enzyme 底层使用了 ,但是更方便,更具可读性,更强大。
npm install —save-dev enzyme@H__9@为了兼容 React 的版本,我们还需要安装 Enzyme 适配器,Enzyme 提供了适配器用以兼容 React16 ,React 15.x,React 0.14.x,React 0.13.x。如果你使用的是 React16,你可以使用下面的命令安装相关依赖:
npm install --save-dev enzyme-adapter-react-16@H__9@为了测试组件,我们创建了 setup() 辅助,用来把模拟过的(stubbed)回调当作 props 传入,然后使用 () 来渲染组件。这样就可以依据 “是否了回调” 的断言来写独立的测试。
示例
import React, { Component } from 'react' import PropTypes from 'prop-types' import TodoTextInput from './TodoTextInput' class Header extends Component { handleSave(text) { if (text.length !== 0) { this.props.addTodo(text) } } render() { return ( <header className="header"> <h1>todos</h1> <TodoTextInput newTodo={true} onSave={this.handleSave.bind(this)} placeholder="What needs to be done?" /> </header> ) } } Header.propTypes = { addTodo: PropTypes.func.is } export default Header@H__9@上面的组件可以这样来测试:
import React from 'react' import Enzyme, { mount } from 'enzyme' import Adapter from 'enzyme-adapter-react-16'; import Header from '../../components/Header' Enzyme.conure({ adapter: new Adapter() }); function setup() { const props = { addTodo: jest.fn() } const enzymeWrapper = mount(<Header {...props} />) return { props, enzymeWrapper } } describe('components', () => { describe('Header', () => { it('should render self and subcomponents', () => { const { enzymeWrapper } = setup() expect(enzymeWrapper.find('header').hasClass('header')).toBe(true) expect(enzymeWrapper.find('h1').text()).toBe('todos') const todoInputProps = enzymeWrapper.find('TodoTextInput').props() expect(todoInputProps.newTodo).toBe(true) expect(todoInputProps.placeholder).toEqual('What needs to be done?') }) it('should call addTodo if length of text is greater than 0', () => { const { enzymeWrapper, props } = setup() const input = enzymeWrapper.find('TodoTextInput') input.props().onSave('') expect(props.addTodo.mock.calls.length).toBe(0) input.props().onSave('Use R') expect(props.addTodo.mock.calls.length).toBe(1) }) }) })@H__9@测试 connected 组件
如果你使用类似 的库,你可能会使用 ,比如 。可以让你把 R state 注入到常规的 React 组件中。
考虑如下 App 组件
import { connect } from 'react-r' class App extends Component { /* ... */ } export default connect(mapStateToProps)(App)@H__9@在单元测试中,通常会这样导入 App 组件:
import App from './App'@H__9@不过,上面这样导入的是通过 connect() 返回的包装组件,并非 App 组件本身,如果你想测试和 R 的整合,这很容易,通过 包裹它后传入用以单元测试的特殊 store 就可以了。但是有时候我们想测试的其实是不带 R store 的组件的渲染。
为了测试 App 组件本身而不用处理装饰器,我们推荐你导出未装饰的组件:
import { connect } from 'react-r' // 命名导出未连接的组件 (测试用) export class App extends Component { /* ... */ } // 认导出已连接的组件 (app 用) export default connect(mapStateToProps)(App)@H__9@由于认导出的组件依旧是包装过的组件,上面中的导入依旧会生效,无须你更改已有的。不过现在你可以通过下面这样的办法导入未装饰的组件了:
import { App } from './App'@H__9@如果你需要导入二者,可以按下面这样做:
import ConnectedApp, { App } from './App'@H__9@在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
并且在 .babelrc 中通过 来配置
{ "presets": ["@babel/preset-env"] }@H__9@然后,在 package.json 中的 scripts 处相关的命令
{ ... "scripts": { ... "test": "jest", "test:watch": "npm test -- --watch" }, ... }@H__9@执行 npm test 可以运行一次测试,执行 npm run test:watch 可以让每当改变时执行测试。
测试 Action Creators
在 R 中,action creators 是返回普通对象的,当我们测试 action creators 时,我们想要测试是否了正确的 action creator 以及是否返回了正确的 action。
示例
export function addTodo(text) { return { type: 'ADD_TODO', text } }@H__9@可以这样来测试:
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: types.ADD_TODO, text } expect(actions.addTodo(text)).toEqual(expectedAction) }) })@H__9@测试异步 Action Creators
对于使用 或者其它中间件的异步 action Creator ,最好完全模拟 R store 来进行测试,可以通过使用 来把中间件应用于模拟的 store,还可以使用 来模拟 HTTP 请求。
示例
import 'cross-fetch/polyfill' 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 fetchTodos() { return dispatch => { dispatch(fetchTodosRequest()) return fetch('http://example.com/todos') .then(res => res.json()) .then(body => dispatch(fetchTodosSuccess(body))) .catch(ex => dispatch(fetchTodosFailure(ex))) } }@H__9@可以这样来测试:
import conureMockStore from 'r-mock-store' import thunk from 'r-thunk' import * as actions from '../../actions/TodoActions' import * as types from '../../constants/ActionTypes' import fetchMock from 'fetch-mock' import expect from 'expect' // 可以使用任何测试库 const middlewares = [thunk] const mockStore = conureMockStore(middlewares) describe('async actions', () => { afterEach(() => { fetchMock.reset() fetchMock.restore() }) it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', () => { fetchMock .getOnce('/todos', { body: { todos: ['do something'] }, headers: { 'content-type': 'application/json' } }) const expectedActions = [ { type: types.FETCH_TODOS_REQUEST }, { type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } } ] const store = mockStore({ todos: [] }) return store.dispatch(actions.fetchTodos()).then(() => { // return of async actions expect(store.getActions()).toEqual(expectedActions) }) }) })@H__9@测试 Reducers
Reducer 把 action 应用到之前的 state,并返回新的 state。示例如下。
示例
import { ADD_TODO } from '../constants/ActionTypes' const initialState = [ { text: 'Use R', 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 } }@H__9@可以这样来测试:
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 R', completed: false, id: 0 } ]) }) it('should handle ADD_TODO', () => { expect( reducer([], { type: types.ADD_TODO, text: 'Run the tests' }) ).toEqual([ { text: 'Run the tests', completed: false, id: 0 } ]) expect( reducer( [ { text: 'Use R', completed: false, id: 0 } ], { type: types.ADD_TODO, text: 'Run the tests' } ) ).toEqual([ { text: 'Run the tests', completed: false, id: 1 }, { text: 'Use R', completed: false, id: 0 } ]) }) })@H__9@测试组件
React 组件有优点,它们通常很小且只依赖于传入的 props ,因此测试起来很简便。
首先,我们需要安装 ,Enzyme 底层使用了 ,但是更方便,更具可读性,更强大。
npm install —save-dev enzyme@H__9@为了兼容 React 的版本,我们还需要安装 Enzyme 适配器,Enzyme 提供了适配器用以兼容 React16 ,React 15.x,React 0.14.x,React 0.13.x。如果你使用的是 React16,你可以使用下面的命令安装相关依赖:
npm install --save-dev enzyme-adapter-react-16@H__9@为了测试组件,我们创建了 setup() 辅助,用来把模拟过的(stubbed)回调当作 props 传入,然后使用 () 来渲染组件。这样就可以依据 “是否了回调” 的断言来写独立的测试。
示例
import React, { Component } from 'react' import PropTypes from 'prop-types' import TodoTextInput from './TodoTextInput' class Header extends Component { handleSave(text) { if (text.length !== 0) { this.props.addTodo(text) } } render() { return ( <header className="header"> <h1>todos</h1> <TodoTextInput newTodo={true} onSave={this.handleSave.bind(this)} placeholder="What needs to be done?" /> </header> ) } } Header.propTypes = { addTodo: PropTypes.func.is } export default Header@H__9@上面的组件可以这样来测试:
import React from 'react' import Enzyme, { mount } from 'enzyme' import Adapter from 'enzyme-adapter-react-16'; import Header from '../../components/Header' Enzyme.conure({ adapter: new Adapter() }); function setup() { const props = { addTodo: jest.fn() } const enzymeWrapper = mount(<Header {...props} />) return { props, enzymeWrapper } } describe('components', () => { describe('Header', () => { it('should render self and subcomponents', () => { const { enzymeWrapper } = setup() expect(enzymeWrapper.find('header').hasClass('header')).toBe(true) expect(enzymeWrapper.find('h1').text()).toBe('todos') const todoInputProps = enzymeWrapper.find('TodoTextInput').props() expect(todoInputProps.newTodo).toBe(true) expect(todoInputProps.placeholder).toEqual('What needs to be done?') }) it('should call addTodo if length of text is greater than 0', () => { const { enzymeWrapper, props } = setup() const input = enzymeWrapper.find('TodoTextInput') input.props().onSave('') expect(props.addTodo.mock.calls.length).toBe(0) input.props().onSave('Use R') expect(props.addTodo.mock.calls.length).toBe(1) }) }) })@H__9@测试 connected 组件
如果你使用类似 的库,你可能会使用 ,比如 。可以让你把 R state 注入到常规的 React 组件中。
考虑如下 App 组件
import { connect } from 'react-r' class App extends Component { /* ... */ } export default connect(mapStateToProps)(App)@H__9@在单元测试中,通常会这样导入 App 组件:
import App from './App'@H__9@不过,上面这样导入的是通过 connect() 返回的包装组件,并非 App 组件本身,如果你想测试和 R 的整合,这很容易,通过 包裹它后传入用以单元测试的特殊 store 就可以了。但是有时候我们想测试的其实是不带 R store 的组件的渲染。
为了测试 App 组件本身而不用处理装饰器,我们推荐你导出未装饰的组件:
import { connect } from 'react-r' // 命名导出未连接的组件 (测试用) export class App extends Component { /* ... */ } // 认导出已连接的组件 (app 用) export default connect(mapStateToProps)(App)@H__9@由于认导出的组件依旧是包装过的组件,上面中的导入依旧会生效,无须你更改已有的。不过现在你可以通过下面这样的办法导入未装饰的组件了:
import { App } from './App'@H__9@如果你需要导入二者,可以按下面这样做:
import ConnectedApp, { App } from './App'@H__9@在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
然后,在 package.json 中的 scripts 处相关的命令
{ ... "scripts": { ... "test": "jest", "test:watch": "npm test -- --watch" }, ... }@H__9@执行 npm test 可以运行一次测试,执行 npm run test:watch 可以让每当改变时执行测试。
测试 Action Creators
在 R 中,action creators 是返回普通对象的,当我们测试 action creators 时,我们想要测试是否了正确的 action creator 以及是否返回了正确的 action。
示例
export function addTodo(text) { return { type: 'ADD_TODO', text } }@H__9@可以这样来测试:
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: types.ADD_TODO, text } expect(actions.addTodo(text)).toEqual(expectedAction) }) })@H__9@测试异步 Action Creators
对于使用 或者其它中间件的异步 action Creator ,最好完全模拟 R store 来进行测试,可以通过使用 来把中间件应用于模拟的 store,还可以使用 来模拟 HTTP 请求。
示例
import 'cross-fetch/polyfill' 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 fetchTodos() { return dispatch => { dispatch(fetchTodosRequest()) return fetch('http://example.com/todos') .then(res => res.json()) .then(body => dispatch(fetchTodosSuccess(body))) .catch(ex => dispatch(fetchTodosFailure(ex))) } }@H__9@可以这样来测试:
import conureMockStore from 'r-mock-store' import thunk from 'r-thunk' import * as actions from '../../actions/TodoActions' import * as types from '../../constants/ActionTypes' import fetchMock from 'fetch-mock' import expect from 'expect' // 可以使用任何测试库 const middlewares = [thunk] const mockStore = conureMockStore(middlewares) describe('async actions', () => { afterEach(() => { fetchMock.reset() fetchMock.restore() }) it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', () => { fetchMock .getOnce('/todos', { body: { todos: ['do something'] }, headers: { 'content-type': 'application/json' } }) const expectedActions = [ { type: types.FETCH_TODOS_REQUEST }, { type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } } ] const store = mockStore({ todos: [] }) return store.dispatch(actions.fetchTodos()).then(() => { // return of async actions expect(store.getActions()).toEqual(expectedActions) }) }) })@H__9@测试 Reducers
Reducer 把 action 应用到之前的 state,并返回新的 state。示例如下。
示例
import { ADD_TODO } from '../constants/ActionTypes' const initialState = [ { text: 'Use R', 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 } }@H__9@可以这样来测试:
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 R', completed: false, id: 0 } ]) }) it('should handle ADD_TODO', () => { expect( reducer([], { type: types.ADD_TODO, text: 'Run the tests' }) ).toEqual([ { text: 'Run the tests', completed: false, id: 0 } ]) expect( reducer( [ { text: 'Use R', completed: false, id: 0 } ], { type: types.ADD_TODO, text: 'Run the tests' } ) ).toEqual([ { text: 'Run the tests', completed: false, id: 1 }, { text: 'Use R', completed: false, id: 0 } ]) }) })@H__9@测试组件
React 组件有优点,它们通常很小且只依赖于传入的 props ,因此测试起来很简便。
首先,我们需要安装 ,Enzyme 底层使用了 ,但是更方便,更具可读性,更强大。
npm install —save-dev enzyme@H__9@为了兼容 React 的版本,我们还需要安装 Enzyme 适配器,Enzyme 提供了适配器用以兼容 React16 ,React 15.x,React 0.14.x,React 0.13.x。如果你使用的是 React16,你可以使用下面的命令安装相关依赖:
npm install --save-dev enzyme-adapter-react-16@H__9@为了测试组件,我们创建了 setup() 辅助,用来把模拟过的(stubbed)回调当作 props 传入,然后使用 () 来渲染组件。这样就可以依据 “是否了回调” 的断言来写独立的测试。
示例
import React, { Component } from 'react' import PropTypes from 'prop-types' import TodoTextInput from './TodoTextInput' class Header extends Component { handleSave(text) { if (text.length !== 0) { this.props.addTodo(text) } } render() { return ( <header className="header"> <h1>todos</h1> <TodoTextInput newTodo={true} onSave={this.handleSave.bind(this)} placeholder="What needs to be done?" /> </header> ) } } Header.propTypes = { addTodo: PropTypes.func.is } export default Header@H__9@上面的组件可以这样来测试:
import React from 'react' import Enzyme, { mount } from 'enzyme' import Adapter from 'enzyme-adapter-react-16'; import Header from '../../components/Header' Enzyme.conure({ adapter: new Adapter() }); function setup() { const props = { addTodo: jest.fn() } const enzymeWrapper = mount(<Header {...props} />) return { props, enzymeWrapper } } describe('components', () => { describe('Header', () => { it('should render self and subcomponents', () => { const { enzymeWrapper } = setup() expect(enzymeWrapper.find('header').hasClass('header')).toBe(true) expect(enzymeWrapper.find('h1').text()).toBe('todos') const todoInputProps = enzymeWrapper.find('TodoTextInput').props() expect(todoInputProps.newTodo).toBe(true) expect(todoInputProps.placeholder).toEqual('What needs to be done?') }) it('should call addTodo if length of text is greater than 0', () => { const { enzymeWrapper, props } = setup() const input = enzymeWrapper.find('TodoTextInput') input.props().onSave('') expect(props.addTodo.mock.calls.length).toBe(0) input.props().onSave('Use R') expect(props.addTodo.mock.calls.length).toBe(1) }) }) })@H__9@测试 connected 组件
如果你使用类似 的库,你可能会使用 ,比如 。可以让你把 R state 注入到常规的 React 组件中。
考虑如下 App 组件
import { connect } from 'react-r' class App extends Component { /* ... */ } export default connect(mapStateToProps)(App)@H__9@在单元测试中,通常会这样导入 App 组件:
import App from './App'@H__9@不过,上面这样导入的是通过 connect() 返回的包装组件,并非 App 组件本身,如果你想测试和 R 的整合,这很容易,通过 包裹它后传入用以单元测试的特殊 store 就可以了。但是有时候我们想测试的其实是不带 R store 的组件的渲染。
为了测试 App 组件本身而不用处理装饰器,我们推荐你导出未装饰的组件:
import { connect } from 'react-r' // 命名导出未连接的组件 (测试用) export class App extends Component { /* ... */ } // 认导出已连接的组件 (app 用) export default connect(mapStateToProps)(App)@H__9@由于认导出的组件依旧是包装过的组件,上面中的导入依旧会生效,无须你更改已有的。不过现在你可以通过下面这样的办法导入未装饰的组件了:
import { App } from './App'@H__9@如果你需要导入二者,可以按下面这样做:
import ConnectedApp, { App } from './App'@H__9@在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
执行 npm test 可以运行一次测试,执行 npm run test:watch 可以让每当改变时执行测试。
在 R 中,action creators 是返回普通对象的,当我们测试 action creators 时,我们想要测试是否了正确的 action creator 以及是否返回了正确的 action。
export function addTodo(text) { return { type: 'ADD_TODO', text } }@H__9@可以这样来测试:
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: types.ADD_TODO, text } expect(actions.addTodo(text)).toEqual(expectedAction) }) })@H__9@测试异步 Action Creators
对于使用 或者其它中间件的异步 action Creator ,最好完全模拟 R store 来进行测试,可以通过使用 来把中间件应用于模拟的 store,还可以使用 来模拟 HTTP 请求。
示例
import 'cross-fetch/polyfill' 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 fetchTodos() { return dispatch => { dispatch(fetchTodosRequest()) return fetch('http://example.com/todos') .then(res => res.json()) .then(body => dispatch(fetchTodosSuccess(body))) .catch(ex => dispatch(fetchTodosFailure(ex))) } }@H__9@可以这样来测试:
import conureMockStore from 'r-mock-store' import thunk from 'r-thunk' import * as actions from '../../actions/TodoActions' import * as types from '../../constants/ActionTypes' import fetchMock from 'fetch-mock' import expect from 'expect' // 可以使用任何测试库 const middlewares = [thunk] const mockStore = conureMockStore(middlewares) describe('async actions', () => { afterEach(() => { fetchMock.reset() fetchMock.restore() }) it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', () => { fetchMock .getOnce('/todos', { body: { todos: ['do something'] }, headers: { 'content-type': 'application/json' } }) const expectedActions = [ { type: types.FETCH_TODOS_REQUEST }, { type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } } ] const store = mockStore({ todos: [] }) return store.dispatch(actions.fetchTodos()).then(() => { // return of async actions expect(store.getActions()).toEqual(expectedActions) }) }) })@H__9@测试 Reducers
Reducer 把 action 应用到之前的 state,并返回新的 state。示例如下。
示例
import { ADD_TODO } from '../constants/ActionTypes' const initialState = [ { text: 'Use R', 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 } }@H__9@可以这样来测试:
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 R', completed: false, id: 0 } ]) }) it('should handle ADD_TODO', () => { expect( reducer([], { type: types.ADD_TODO, text: 'Run the tests' }) ).toEqual([ { text: 'Run the tests', completed: false, id: 0 } ]) expect( reducer( [ { text: 'Use R', completed: false, id: 0 } ], { type: types.ADD_TODO, text: 'Run the tests' } ) ).toEqual([ { text: 'Run the tests', completed: false, id: 1 }, { text: 'Use R', completed: false, id: 0 } ]) }) })@H__9@测试组件
React 组件有优点,它们通常很小且只依赖于传入的 props ,因此测试起来很简便。
首先,我们需要安装 ,Enzyme 底层使用了 ,但是更方便,更具可读性,更强大。
npm install —save-dev enzyme@H__9@为了兼容 React 的版本,我们还需要安装 Enzyme 适配器,Enzyme 提供了适配器用以兼容 React16 ,React 15.x,React 0.14.x,React 0.13.x。如果你使用的是 React16,你可以使用下面的命令安装相关依赖:
npm install --save-dev enzyme-adapter-react-16@H__9@为了测试组件,我们创建了 setup() 辅助,用来把模拟过的(stubbed)回调当作 props 传入,然后使用 () 来渲染组件。这样就可以依据 “是否了回调” 的断言来写独立的测试。
示例
import React, { Component } from 'react' import PropTypes from 'prop-types' import TodoTextInput from './TodoTextInput' class Header extends Component { handleSave(text) { if (text.length !== 0) { this.props.addTodo(text) } } render() { return ( <header className="header"> <h1>todos</h1> <TodoTextInput newTodo={true} onSave={this.handleSave.bind(this)} placeholder="What needs to be done?" /> </header> ) } } Header.propTypes = { addTodo: PropTypes.func.is } export default Header@H__9@上面的组件可以这样来测试:
import React from 'react' import Enzyme, { mount } from 'enzyme' import Adapter from 'enzyme-adapter-react-16'; import Header from '../../components/Header' Enzyme.conure({ adapter: new Adapter() }); function setup() { const props = { addTodo: jest.fn() } const enzymeWrapper = mount(<Header {...props} />) return { props, enzymeWrapper } } describe('components', () => { describe('Header', () => { it('should render self and subcomponents', () => { const { enzymeWrapper } = setup() expect(enzymeWrapper.find('header').hasClass('header')).toBe(true) expect(enzymeWrapper.find('h1').text()).toBe('todos') const todoInputProps = enzymeWrapper.find('TodoTextInput').props() expect(todoInputProps.newTodo).toBe(true) expect(todoInputProps.placeholder).toEqual('What needs to be done?') }) it('should call addTodo if length of text is greater than 0', () => { const { enzymeWrapper, props } = setup() const input = enzymeWrapper.find('TodoTextInput') input.props().onSave('') expect(props.addTodo.mock.calls.length).toBe(0) input.props().onSave('Use R') expect(props.addTodo.mock.calls.length).toBe(1) }) }) })@H__9@测试 connected 组件
如果你使用类似 的库,你可能会使用 ,比如 。可以让你把 R state 注入到常规的 React 组件中。
考虑如下 App 组件
import { connect } from 'react-r' class App extends Component { /* ... */ } export default connect(mapStateToProps)(App)@H__9@在单元测试中,通常会这样导入 App 组件:
import App from './App'@H__9@不过,上面这样导入的是通过 connect() 返回的包装组件,并非 App 组件本身,如果你想测试和 R 的整合,这很容易,通过 包裹它后传入用以单元测试的特殊 store 就可以了。但是有时候我们想测试的其实是不带 R store 的组件的渲染。
为了测试 App 组件本身而不用处理装饰器,我们推荐你导出未装饰的组件:
import { connect } from 'react-r' // 命名导出未连接的组件 (测试用) export class App extends Component { /* ... */ } // 认导出已连接的组件 (app 用) export default connect(mapStateToProps)(App)@H__9@由于认导出的组件依旧是包装过的组件,上面中的导入依旧会生效,无须你更改已有的。不过现在你可以通过下面这样的办法导入未装饰的组件了:
import { App } from './App'@H__9@如果你需要导入二者,可以按下面这样做:
import ConnectedApp, { App } from './App'@H__9@在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
可以这样来测试:
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: types.ADD_TODO, text } expect(actions.addTodo(text)).toEqual(expectedAction) }) })@H__9@测试异步 Action Creators
对于使用 或者其它中间件的异步 action Creator ,最好完全模拟 R store 来进行测试,可以通过使用 来把中间件应用于模拟的 store,还可以使用 来模拟 HTTP 请求。
示例
import 'cross-fetch/polyfill' 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 fetchTodos() { return dispatch => { dispatch(fetchTodosRequest()) return fetch('http://example.com/todos') .then(res => res.json()) .then(body => dispatch(fetchTodosSuccess(body))) .catch(ex => dispatch(fetchTodosFailure(ex))) } }@H__9@可以这样来测试:
import conureMockStore from 'r-mock-store' import thunk from 'r-thunk' import * as actions from '../../actions/TodoActions' import * as types from '../../constants/ActionTypes' import fetchMock from 'fetch-mock' import expect from 'expect' // 可以使用任何测试库 const middlewares = [thunk] const mockStore = conureMockStore(middlewares) describe('async actions', () => { afterEach(() => { fetchMock.reset() fetchMock.restore() }) it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', () => { fetchMock .getOnce('/todos', { body: { todos: ['do something'] }, headers: { 'content-type': 'application/json' } }) const expectedActions = [ { type: types.FETCH_TODOS_REQUEST }, { type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } } ] const store = mockStore({ todos: [] }) return store.dispatch(actions.fetchTodos()).then(() => { // return of async actions expect(store.getActions()).toEqual(expectedActions) }) }) })@H__9@测试 Reducers
Reducer 把 action 应用到之前的 state,并返回新的 state。示例如下。
示例
import { ADD_TODO } from '../constants/ActionTypes' const initialState = [ { text: 'Use R', 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 } }@H__9@可以这样来测试:
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 R', completed: false, id: 0 } ]) }) it('should handle ADD_TODO', () => { expect( reducer([], { type: types.ADD_TODO, text: 'Run the tests' }) ).toEqual([ { text: 'Run the tests', completed: false, id: 0 } ]) expect( reducer( [ { text: 'Use R', completed: false, id: 0 } ], { type: types.ADD_TODO, text: 'Run the tests' } ) ).toEqual([ { text: 'Run the tests', completed: false, id: 1 }, { text: 'Use R', completed: false, id: 0 } ]) }) })@H__9@测试组件
React 组件有优点,它们通常很小且只依赖于传入的 props ,因此测试起来很简便。
首先,我们需要安装 ,Enzyme 底层使用了 ,但是更方便,更具可读性,更强大。
npm install —save-dev enzyme@H__9@为了兼容 React 的版本,我们还需要安装 Enzyme 适配器,Enzyme 提供了适配器用以兼容 React16 ,React 15.x,React 0.14.x,React 0.13.x。如果你使用的是 React16,你可以使用下面的命令安装相关依赖:
npm install --save-dev enzyme-adapter-react-16@H__9@为了测试组件,我们创建了 setup() 辅助,用来把模拟过的(stubbed)回调当作 props 传入,然后使用 () 来渲染组件。这样就可以依据 “是否了回调” 的断言来写独立的测试。
示例
import React, { Component } from 'react' import PropTypes from 'prop-types' import TodoTextInput from './TodoTextInput' class Header extends Component { handleSave(text) { if (text.length !== 0) { this.props.addTodo(text) } } render() { return ( <header className="header"> <h1>todos</h1> <TodoTextInput newTodo={true} onSave={this.handleSave.bind(this)} placeholder="What needs to be done?" /> </header> ) } } Header.propTypes = { addTodo: PropTypes.func.is } export default Header@H__9@上面的组件可以这样来测试:
import React from 'react' import Enzyme, { mount } from 'enzyme' import Adapter from 'enzyme-adapter-react-16'; import Header from '../../components/Header' Enzyme.conure({ adapter: new Adapter() }); function setup() { const props = { addTodo: jest.fn() } const enzymeWrapper = mount(<Header {...props} />) return { props, enzymeWrapper } } describe('components', () => { describe('Header', () => { it('should render self and subcomponents', () => { const { enzymeWrapper } = setup() expect(enzymeWrapper.find('header').hasClass('header')).toBe(true) expect(enzymeWrapper.find('h1').text()).toBe('todos') const todoInputProps = enzymeWrapper.find('TodoTextInput').props() expect(todoInputProps.newTodo).toBe(true) expect(todoInputProps.placeholder).toEqual('What needs to be done?') }) it('should call addTodo if length of text is greater than 0', () => { const { enzymeWrapper, props } = setup() const input = enzymeWrapper.find('TodoTextInput') input.props().onSave('') expect(props.addTodo.mock.calls.length).toBe(0) input.props().onSave('Use R') expect(props.addTodo.mock.calls.length).toBe(1) }) }) })@H__9@测试 connected 组件
如果你使用类似 的库,你可能会使用 ,比如 。可以让你把 R state 注入到常规的 React 组件中。
考虑如下 App 组件
import { connect } from 'react-r' class App extends Component { /* ... */ } export default connect(mapStateToProps)(App)@H__9@在单元测试中,通常会这样导入 App 组件:
import App from './App'@H__9@不过,上面这样导入的是通过 connect() 返回的包装组件,并非 App 组件本身,如果你想测试和 R 的整合,这很容易,通过 包裹它后传入用以单元测试的特殊 store 就可以了。但是有时候我们想测试的其实是不带 R store 的组件的渲染。
为了测试 App 组件本身而不用处理装饰器,我们推荐你导出未装饰的组件:
import { connect } from 'react-r' // 命名导出未连接的组件 (测试用) export class App extends Component { /* ... */ } // 认导出已连接的组件 (app 用) export default connect(mapStateToProps)(App)@H__9@由于认导出的组件依旧是包装过的组件,上面中的导入依旧会生效,无须你更改已有的。不过现在你可以通过下面这样的办法导入未装饰的组件了:
import { App } from './App'@H__9@如果你需要导入二者,可以按下面这样做:
import ConnectedApp, { App } from './App'@H__9@在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
对于使用 或者其它中间件的异步 action Creator ,最好完全模拟 R store 来进行测试,可以通过使用 来把中间件应用于模拟的 store,还可以使用 来模拟 HTTP 请求。
import 'cross-fetch/polyfill' 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 fetchTodos() { return dispatch => { dispatch(fetchTodosRequest()) return fetch('http://example.com/todos') .then(res => res.json()) .then(body => dispatch(fetchTodosSuccess(body))) .catch(ex => dispatch(fetchTodosFailure(ex))) } }@H__9@可以这样来测试:
import conureMockStore from 'r-mock-store' import thunk from 'r-thunk' import * as actions from '../../actions/TodoActions' import * as types from '../../constants/ActionTypes' import fetchMock from 'fetch-mock' import expect from 'expect' // 可以使用任何测试库 const middlewares = [thunk] const mockStore = conureMockStore(middlewares) describe('async actions', () => { afterEach(() => { fetchMock.reset() fetchMock.restore() }) it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', () => { fetchMock .getOnce('/todos', { body: { todos: ['do something'] }, headers: { 'content-type': 'application/json' } }) const expectedActions = [ { type: types.FETCH_TODOS_REQUEST }, { type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } } ] const store = mockStore({ todos: [] }) return store.dispatch(actions.fetchTodos()).then(() => { // return of async actions expect(store.getActions()).toEqual(expectedActions) }) }) })@H__9@测试 Reducers
Reducer 把 action 应用到之前的 state,并返回新的 state。示例如下。
示例
import { ADD_TODO } from '../constants/ActionTypes' const initialState = [ { text: 'Use R', 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 } }@H__9@可以这样来测试:
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 R', completed: false, id: 0 } ]) }) it('should handle ADD_TODO', () => { expect( reducer([], { type: types.ADD_TODO, text: 'Run the tests' }) ).toEqual([ { text: 'Run the tests', completed: false, id: 0 } ]) expect( reducer( [ { text: 'Use R', completed: false, id: 0 } ], { type: types.ADD_TODO, text: 'Run the tests' } ) ).toEqual([ { text: 'Run the tests', completed: false, id: 1 }, { text: 'Use R', completed: false, id: 0 } ]) }) })@H__9@测试组件
React 组件有优点,它们通常很小且只依赖于传入的 props ,因此测试起来很简便。
首先,我们需要安装 ,Enzyme 底层使用了 ,但是更方便,更具可读性,更强大。
npm install —save-dev enzyme@H__9@为了兼容 React 的版本,我们还需要安装 Enzyme 适配器,Enzyme 提供了适配器用以兼容 React16 ,React 15.x,React 0.14.x,React 0.13.x。如果你使用的是 React16,你可以使用下面的命令安装相关依赖:
npm install --save-dev enzyme-adapter-react-16@H__9@为了测试组件,我们创建了 setup() 辅助,用来把模拟过的(stubbed)回调当作 props 传入,然后使用 () 来渲染组件。这样就可以依据 “是否了回调” 的断言来写独立的测试。
示例
import React, { Component } from 'react' import PropTypes from 'prop-types' import TodoTextInput from './TodoTextInput' class Header extends Component { handleSave(text) { if (text.length !== 0) { this.props.addTodo(text) } } render() { return ( <header className="header"> <h1>todos</h1> <TodoTextInput newTodo={true} onSave={this.handleSave.bind(this)} placeholder="What needs to be done?" /> </header> ) } } Header.propTypes = { addTodo: PropTypes.func.is } export default Header@H__9@上面的组件可以这样来测试:
import React from 'react' import Enzyme, { mount } from 'enzyme' import Adapter from 'enzyme-adapter-react-16'; import Header from '../../components/Header' Enzyme.conure({ adapter: new Adapter() }); function setup() { const props = { addTodo: jest.fn() } const enzymeWrapper = mount(<Header {...props} />) return { props, enzymeWrapper } } describe('components', () => { describe('Header', () => { it('should render self and subcomponents', () => { const { enzymeWrapper } = setup() expect(enzymeWrapper.find('header').hasClass('header')).toBe(true) expect(enzymeWrapper.find('h1').text()).toBe('todos') const todoInputProps = enzymeWrapper.find('TodoTextInput').props() expect(todoInputProps.newTodo).toBe(true) expect(todoInputProps.placeholder).toEqual('What needs to be done?') }) it('should call addTodo if length of text is greater than 0', () => { const { enzymeWrapper, props } = setup() const input = enzymeWrapper.find('TodoTextInput') input.props().onSave('') expect(props.addTodo.mock.calls.length).toBe(0) input.props().onSave('Use R') expect(props.addTodo.mock.calls.length).toBe(1) }) }) })@H__9@测试 connected 组件
如果你使用类似 的库,你可能会使用 ,比如 。可以让你把 R state 注入到常规的 React 组件中。
考虑如下 App 组件
import { connect } from 'react-r' class App extends Component { /* ... */ } export default connect(mapStateToProps)(App)@H__9@在单元测试中,通常会这样导入 App 组件:
import App from './App'@H__9@不过,上面这样导入的是通过 connect() 返回的包装组件,并非 App 组件本身,如果你想测试和 R 的整合,这很容易,通过 包裹它后传入用以单元测试的特殊 store 就可以了。但是有时候我们想测试的其实是不带 R store 的组件的渲染。
为了测试 App 组件本身而不用处理装饰器,我们推荐你导出未装饰的组件:
import { connect } from 'react-r' // 命名导出未连接的组件 (测试用) export class App extends Component { /* ... */ } // 认导出已连接的组件 (app 用) export default connect(mapStateToProps)(App)@H__9@由于认导出的组件依旧是包装过的组件,上面中的导入依旧会生效,无须你更改已有的。不过现在你可以通过下面这样的办法导入未装饰的组件了:
import { App } from './App'@H__9@如果你需要导入二者,可以按下面这样做:
import ConnectedApp, { App } from './App'@H__9@在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
可以这样来测试:
import conureMockStore from 'r-mock-store' import thunk from 'r-thunk' import * as actions from '../../actions/TodoActions' import * as types from '../../constants/ActionTypes' import fetchMock from 'fetch-mock' import expect from 'expect' // 可以使用任何测试库 const middlewares = [thunk] const mockStore = conureMockStore(middlewares) describe('async actions', () => { afterEach(() => { fetchMock.reset() fetchMock.restore() }) it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', () => { fetchMock .getOnce('/todos', { body: { todos: ['do something'] }, headers: { 'content-type': 'application/json' } }) const expectedActions = [ { type: types.FETCH_TODOS_REQUEST }, { type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } } ] const store = mockStore({ todos: [] }) return store.dispatch(actions.fetchTodos()).then(() => { // return of async actions expect(store.getActions()).toEqual(expectedActions) }) }) })@H__9@测试 Reducers
Reducer 把 action 应用到之前的 state,并返回新的 state。示例如下。
示例
import { ADD_TODO } from '../constants/ActionTypes' const initialState = [ { text: 'Use R', 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 } }@H__9@可以这样来测试:
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 R', completed: false, id: 0 } ]) }) it('should handle ADD_TODO', () => { expect( reducer([], { type: types.ADD_TODO, text: 'Run the tests' }) ).toEqual([ { text: 'Run the tests', completed: false, id: 0 } ]) expect( reducer( [ { text: 'Use R', completed: false, id: 0 } ], { type: types.ADD_TODO, text: 'Run the tests' } ) ).toEqual([ { text: 'Run the tests', completed: false, id: 1 }, { text: 'Use R', completed: false, id: 0 } ]) }) })@H__9@测试组件
React 组件有优点,它们通常很小且只依赖于传入的 props ,因此测试起来很简便。
首先,我们需要安装 ,Enzyme 底层使用了 ,但是更方便,更具可读性,更强大。
npm install —save-dev enzyme@H__9@为了兼容 React 的版本,我们还需要安装 Enzyme 适配器,Enzyme 提供了适配器用以兼容 React16 ,React 15.x,React 0.14.x,React 0.13.x。如果你使用的是 React16,你可以使用下面的命令安装相关依赖:
npm install --save-dev enzyme-adapter-react-16@H__9@为了测试组件,我们创建了 setup() 辅助,用来把模拟过的(stubbed)回调当作 props 传入,然后使用 () 来渲染组件。这样就可以依据 “是否了回调” 的断言来写独立的测试。
示例
import React, { Component } from 'react' import PropTypes from 'prop-types' import TodoTextInput from './TodoTextInput' class Header extends Component { handleSave(text) { if (text.length !== 0) { this.props.addTodo(text) } } render() { return ( <header className="header"> <h1>todos</h1> <TodoTextInput newTodo={true} onSave={this.handleSave.bind(this)} placeholder="What needs to be done?" /> </header> ) } } Header.propTypes = { addTodo: PropTypes.func.is } export default Header@H__9@上面的组件可以这样来测试:
import React from 'react' import Enzyme, { mount } from 'enzyme' import Adapter from 'enzyme-adapter-react-16'; import Header from '../../components/Header' Enzyme.conure({ adapter: new Adapter() }); function setup() { const props = { addTodo: jest.fn() } const enzymeWrapper = mount(<Header {...props} />) return { props, enzymeWrapper } } describe('components', () => { describe('Header', () => { it('should render self and subcomponents', () => { const { enzymeWrapper } = setup() expect(enzymeWrapper.find('header').hasClass('header')).toBe(true) expect(enzymeWrapper.find('h1').text()).toBe('todos') const todoInputProps = enzymeWrapper.find('TodoTextInput').props() expect(todoInputProps.newTodo).toBe(true) expect(todoInputProps.placeholder).toEqual('What needs to be done?') }) it('should call addTodo if length of text is greater than 0', () => { const { enzymeWrapper, props } = setup() const input = enzymeWrapper.find('TodoTextInput') input.props().onSave('') expect(props.addTodo.mock.calls.length).toBe(0) input.props().onSave('Use R') expect(props.addTodo.mock.calls.length).toBe(1) }) }) })@H__9@测试 connected 组件
如果你使用类似 的库,你可能会使用 ,比如 。可以让你把 R state 注入到常规的 React 组件中。
考虑如下 App 组件
import { connect } from 'react-r' class App extends Component { /* ... */ } export default connect(mapStateToProps)(App)@H__9@在单元测试中,通常会这样导入 App 组件:
import App from './App'@H__9@不过,上面这样导入的是通过 connect() 返回的包装组件,并非 App 组件本身,如果你想测试和 R 的整合,这很容易,通过 包裹它后传入用以单元测试的特殊 store 就可以了。但是有时候我们想测试的其实是不带 R store 的组件的渲染。
为了测试 App 组件本身而不用处理装饰器,我们推荐你导出未装饰的组件:
import { connect } from 'react-r' // 命名导出未连接的组件 (测试用) export class App extends Component { /* ... */ } // 认导出已连接的组件 (app 用) export default connect(mapStateToProps)(App)@H__9@由于认导出的组件依旧是包装过的组件,上面中的导入依旧会生效,无须你更改已有的。不过现在你可以通过下面这样的办法导入未装饰的组件了:
import { App } from './App'@H__9@如果你需要导入二者,可以按下面这样做:
import ConnectedApp, { App } from './App'@H__9@在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
Reducer 把 action 应用到之前的 state,并返回新的 state。示例如下。
import { ADD_TODO } from '../constants/ActionTypes' const initialState = [ { text: 'Use R', 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 } }@H__9@可以这样来测试:
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 R', completed: false, id: 0 } ]) }) it('should handle ADD_TODO', () => { expect( reducer([], { type: types.ADD_TODO, text: 'Run the tests' }) ).toEqual([ { text: 'Run the tests', completed: false, id: 0 } ]) expect( reducer( [ { text: 'Use R', completed: false, id: 0 } ], { type: types.ADD_TODO, text: 'Run the tests' } ) ).toEqual([ { text: 'Run the tests', completed: false, id: 1 }, { text: 'Use R', completed: false, id: 0 } ]) }) })@H__9@测试组件
React 组件有优点,它们通常很小且只依赖于传入的 props ,因此测试起来很简便。
首先,我们需要安装 ,Enzyme 底层使用了 ,但是更方便,更具可读性,更强大。
npm install —save-dev enzyme@H__9@为了兼容 React 的版本,我们还需要安装 Enzyme 适配器,Enzyme 提供了适配器用以兼容 React16 ,React 15.x,React 0.14.x,React 0.13.x。如果你使用的是 React16,你可以使用下面的命令安装相关依赖:
npm install --save-dev enzyme-adapter-react-16@H__9@为了测试组件,我们创建了 setup() 辅助,用来把模拟过的(stubbed)回调当作 props 传入,然后使用 () 来渲染组件。这样就可以依据 “是否了回调” 的断言来写独立的测试。
示例
import React, { Component } from 'react' import PropTypes from 'prop-types' import TodoTextInput from './TodoTextInput' class Header extends Component { handleSave(text) { if (text.length !== 0) { this.props.addTodo(text) } } render() { return ( <header className="header"> <h1>todos</h1> <TodoTextInput newTodo={true} onSave={this.handleSave.bind(this)} placeholder="What needs to be done?" /> </header> ) } } Header.propTypes = { addTodo: PropTypes.func.is } export default Header@H__9@上面的组件可以这样来测试:
import React from 'react' import Enzyme, { mount } from 'enzyme' import Adapter from 'enzyme-adapter-react-16'; import Header from '../../components/Header' Enzyme.conure({ adapter: new Adapter() }); function setup() { const props = { addTodo: jest.fn() } const enzymeWrapper = mount(<Header {...props} />) return { props, enzymeWrapper } } describe('components', () => { describe('Header', () => { it('should render self and subcomponents', () => { const { enzymeWrapper } = setup() expect(enzymeWrapper.find('header').hasClass('header')).toBe(true) expect(enzymeWrapper.find('h1').text()).toBe('todos') const todoInputProps = enzymeWrapper.find('TodoTextInput').props() expect(todoInputProps.newTodo).toBe(true) expect(todoInputProps.placeholder).toEqual('What needs to be done?') }) it('should call addTodo if length of text is greater than 0', () => { const { enzymeWrapper, props } = setup() const input = enzymeWrapper.find('TodoTextInput') input.props().onSave('') expect(props.addTodo.mock.calls.length).toBe(0) input.props().onSave('Use R') expect(props.addTodo.mock.calls.length).toBe(1) }) }) })@H__9@测试 connected 组件
如果你使用类似 的库,你可能会使用 ,比如 。可以让你把 R state 注入到常规的 React 组件中。
考虑如下 App 组件
import { connect } from 'react-r' class App extends Component { /* ... */ } export default connect(mapStateToProps)(App)@H__9@在单元测试中,通常会这样导入 App 组件:
import App from './App'@H__9@不过,上面这样导入的是通过 connect() 返回的包装组件,并非 App 组件本身,如果你想测试和 R 的整合,这很容易,通过 包裹它后传入用以单元测试的特殊 store 就可以了。但是有时候我们想测试的其实是不带 R store 的组件的渲染。
为了测试 App 组件本身而不用处理装饰器,我们推荐你导出未装饰的组件:
import { connect } from 'react-r' // 命名导出未连接的组件 (测试用) export class App extends Component { /* ... */ } // 认导出已连接的组件 (app 用) export default connect(mapStateToProps)(App)@H__9@由于认导出的组件依旧是包装过的组件,上面中的导入依旧会生效,无须你更改已有的。不过现在你可以通过下面这样的办法导入未装饰的组件了:
import { App } from './App'@H__9@如果你需要导入二者,可以按下面这样做:
import ConnectedApp, { App } from './App'@H__9@在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
可以这样来测试:
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 R', completed: false, id: 0 } ]) }) it('should handle ADD_TODO', () => { expect( reducer([], { type: types.ADD_TODO, text: 'Run the tests' }) ).toEqual([ { text: 'Run the tests', completed: false, id: 0 } ]) expect( reducer( [ { text: 'Use R', completed: false, id: 0 } ], { type: types.ADD_TODO, text: 'Run the tests' } ) ).toEqual([ { text: 'Run the tests', completed: false, id: 1 }, { text: 'Use R', completed: false, id: 0 } ]) }) })@H__9@测试组件
React 组件有优点,它们通常很小且只依赖于传入的 props ,因此测试起来很简便。
首先,我们需要安装 ,Enzyme 底层使用了 ,但是更方便,更具可读性,更强大。
npm install —save-dev enzyme@H__9@为了兼容 React 的版本,我们还需要安装 Enzyme 适配器,Enzyme 提供了适配器用以兼容 React16 ,React 15.x,React 0.14.x,React 0.13.x。如果你使用的是 React16,你可以使用下面的命令安装相关依赖:
npm install --save-dev enzyme-adapter-react-16@H__9@为了测试组件,我们创建了 setup() 辅助,用来把模拟过的(stubbed)回调当作 props 传入,然后使用 () 来渲染组件。这样就可以依据 “是否了回调” 的断言来写独立的测试。
示例
import React, { Component } from 'react' import PropTypes from 'prop-types' import TodoTextInput from './TodoTextInput' class Header extends Component { handleSave(text) { if (text.length !== 0) { this.props.addTodo(text) } } render() { return ( <header className="header"> <h1>todos</h1> <TodoTextInput newTodo={true} onSave={this.handleSave.bind(this)} placeholder="What needs to be done?" /> </header> ) } } Header.propTypes = { addTodo: PropTypes.func.is } export default Header@H__9@上面的组件可以这样来测试:
import React from 'react' import Enzyme, { mount } from 'enzyme' import Adapter from 'enzyme-adapter-react-16'; import Header from '../../components/Header' Enzyme.conure({ adapter: new Adapter() }); function setup() { const props = { addTodo: jest.fn() } const enzymeWrapper = mount(<Header {...props} />) return { props, enzymeWrapper } } describe('components', () => { describe('Header', () => { it('should render self and subcomponents', () => { const { enzymeWrapper } = setup() expect(enzymeWrapper.find('header').hasClass('header')).toBe(true) expect(enzymeWrapper.find('h1').text()).toBe('todos') const todoInputProps = enzymeWrapper.find('TodoTextInput').props() expect(todoInputProps.newTodo).toBe(true) expect(todoInputProps.placeholder).toEqual('What needs to be done?') }) it('should call addTodo if length of text is greater than 0', () => { const { enzymeWrapper, props } = setup() const input = enzymeWrapper.find('TodoTextInput') input.props().onSave('') expect(props.addTodo.mock.calls.length).toBe(0) input.props().onSave('Use R') expect(props.addTodo.mock.calls.length).toBe(1) }) }) })@H__9@测试 connected 组件
如果你使用类似 的库,你可能会使用 ,比如 。可以让你把 R state 注入到常规的 React 组件中。
考虑如下 App 组件
import { connect } from 'react-r' class App extends Component { /* ... */ } export default connect(mapStateToProps)(App)@H__9@在单元测试中,通常会这样导入 App 组件:
import App from './App'@H__9@不过,上面这样导入的是通过 connect() 返回的包装组件,并非 App 组件本身,如果你想测试和 R 的整合,这很容易,通过 包裹它后传入用以单元测试的特殊 store 就可以了。但是有时候我们想测试的其实是不带 R store 的组件的渲染。
为了测试 App 组件本身而不用处理装饰器,我们推荐你导出未装饰的组件:
import { connect } from 'react-r' // 命名导出未连接的组件 (测试用) export class App extends Component { /* ... */ } // 认导出已连接的组件 (app 用) export default connect(mapStateToProps)(App)@H__9@由于认导出的组件依旧是包装过的组件,上面中的导入依旧会生效,无须你更改已有的。不过现在你可以通过下面这样的办法导入未装饰的组件了:
import { App } from './App'@H__9@如果你需要导入二者,可以按下面这样做:
import ConnectedApp, { App } from './App'@H__9@在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
React 组件有优点,它们通常很小且只依赖于传入的 props ,因此测试起来很简便。
首先,我们需要安装 ,Enzyme 底层使用了 ,但是更方便,更具可读性,更强大。
npm install —save-dev enzyme@H__9@为了兼容 React 的版本,我们还需要安装 Enzyme 适配器,Enzyme 提供了适配器用以兼容 React16 ,React 15.x,React 0.14.x,React 0.13.x。如果你使用的是 React16,你可以使用下面的命令安装相关依赖:
npm install --save-dev enzyme-adapter-react-16@H__9@为了测试组件,我们创建了 setup() 辅助,用来把模拟过的(stubbed)回调当作 props 传入,然后使用 () 来渲染组件。这样就可以依据 “是否了回调” 的断言来写独立的测试。
示例
import React, { Component } from 'react' import PropTypes from 'prop-types' import TodoTextInput from './TodoTextInput' class Header extends Component { handleSave(text) { if (text.length !== 0) { this.props.addTodo(text) } } render() { return ( <header className="header"> <h1>todos</h1> <TodoTextInput newTodo={true} onSave={this.handleSave.bind(this)} placeholder="What needs to be done?" /> </header> ) } } Header.propTypes = { addTodo: PropTypes.func.is } export default Header@H__9@上面的组件可以这样来测试:
import React from 'react' import Enzyme, { mount } from 'enzyme' import Adapter from 'enzyme-adapter-react-16'; import Header from '../../components/Header' Enzyme.conure({ adapter: new Adapter() }); function setup() { const props = { addTodo: jest.fn() } const enzymeWrapper = mount(<Header {...props} />) return { props, enzymeWrapper } } describe('components', () => { describe('Header', () => { it('should render self and subcomponents', () => { const { enzymeWrapper } = setup() expect(enzymeWrapper.find('header').hasClass('header')).toBe(true) expect(enzymeWrapper.find('h1').text()).toBe('todos') const todoInputProps = enzymeWrapper.find('TodoTextInput').props() expect(todoInputProps.newTodo).toBe(true) expect(todoInputProps.placeholder).toEqual('What needs to be done?') }) it('should call addTodo if length of text is greater than 0', () => { const { enzymeWrapper, props } = setup() const input = enzymeWrapper.find('TodoTextInput') input.props().onSave('') expect(props.addTodo.mock.calls.length).toBe(0) input.props().onSave('Use R') expect(props.addTodo.mock.calls.length).toBe(1) }) }) })@H__9@测试 connected 组件
如果你使用类似 的库,你可能会使用 ,比如 。可以让你把 R state 注入到常规的 React 组件中。
考虑如下 App 组件
import { connect } from 'react-r' class App extends Component { /* ... */ } export default connect(mapStateToProps)(App)@H__9@在单元测试中,通常会这样导入 App 组件:
import App from './App'@H__9@不过,上面这样导入的是通过 connect() 返回的包装组件,并非 App 组件本身,如果你想测试和 R 的整合,这很容易,通过 包裹它后传入用以单元测试的特殊 store 就可以了。但是有时候我们想测试的其实是不带 R store 的组件的渲染。
为了测试 App 组件本身而不用处理装饰器,我们推荐你导出未装饰的组件:
import { connect } from 'react-r' // 命名导出未连接的组件 (测试用) export class App extends Component { /* ... */ } // 认导出已连接的组件 (app 用) export default connect(mapStateToProps)(App)@H__9@由于认导出的组件依旧是包装过的组件,上面中的导入依旧会生效,无须你更改已有的。不过现在你可以通过下面这样的办法导入未装饰的组件了:
import { App } from './App'@H__9@如果你需要导入二者,可以按下面这样做:
import ConnectedApp, { App } from './App'@H__9@在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
为了兼容 React 的版本,我们还需要安装 Enzyme 适配器,Enzyme 提供了适配器用以兼容 React16 ,React 15.x,React 0.14.x,React 0.13.x。如果你使用的是 React16,你可以使用下面的命令安装相关依赖:
npm install --save-dev enzyme-adapter-react-16@H__9@为了测试组件,我们创建了 setup() 辅助,用来把模拟过的(stubbed)回调当作 props 传入,然后使用 () 来渲染组件。这样就可以依据 “是否了回调” 的断言来写独立的测试。
示例
import React, { Component } from 'react' import PropTypes from 'prop-types' import TodoTextInput from './TodoTextInput' class Header extends Component { handleSave(text) { if (text.length !== 0) { this.props.addTodo(text) } } render() { return ( <header className="header"> <h1>todos</h1> <TodoTextInput newTodo={true} onSave={this.handleSave.bind(this)} placeholder="What needs to be done?" /> </header> ) } } Header.propTypes = { addTodo: PropTypes.func.is } export default Header@H__9@上面的组件可以这样来测试:
import React from 'react' import Enzyme, { mount } from 'enzyme' import Adapter from 'enzyme-adapter-react-16'; import Header from '../../components/Header' Enzyme.conure({ adapter: new Adapter() }); function setup() { const props = { addTodo: jest.fn() } const enzymeWrapper = mount(<Header {...props} />) return { props, enzymeWrapper } } describe('components', () => { describe('Header', () => { it('should render self and subcomponents', () => { const { enzymeWrapper } = setup() expect(enzymeWrapper.find('header').hasClass('header')).toBe(true) expect(enzymeWrapper.find('h1').text()).toBe('todos') const todoInputProps = enzymeWrapper.find('TodoTextInput').props() expect(todoInputProps.newTodo).toBe(true) expect(todoInputProps.placeholder).toEqual('What needs to be done?') }) it('should call addTodo if length of text is greater than 0', () => { const { enzymeWrapper, props } = setup() const input = enzymeWrapper.find('TodoTextInput') input.props().onSave('') expect(props.addTodo.mock.calls.length).toBe(0) input.props().onSave('Use R') expect(props.addTodo.mock.calls.length).toBe(1) }) }) })@H__9@测试 connected 组件
如果你使用类似 的库,你可能会使用 ,比如 。可以让你把 R state 注入到常规的 React 组件中。
考虑如下 App 组件
import { connect } from 'react-r' class App extends Component { /* ... */ } export default connect(mapStateToProps)(App)@H__9@在单元测试中,通常会这样导入 App 组件:
import App from './App'@H__9@不过,上面这样导入的是通过 connect() 返回的包装组件,并非 App 组件本身,如果你想测试和 R 的整合,这很容易,通过 包裹它后传入用以单元测试的特殊 store 就可以了。但是有时候我们想测试的其实是不带 R store 的组件的渲染。
为了测试 App 组件本身而不用处理装饰器,我们推荐你导出未装饰的组件:
import { connect } from 'react-r' // 命名导出未连接的组件 (测试用) export class App extends Component { /* ... */ } // 认导出已连接的组件 (app 用) export default connect(mapStateToProps)(App)@H__9@由于认导出的组件依旧是包装过的组件,上面中的导入依旧会生效,无须你更改已有的。不过现在你可以通过下面这样的办法导入未装饰的组件了:
import { App } from './App'@H__9@如果你需要导入二者,可以按下面这样做:
import ConnectedApp, { App } from './App'@H__9@在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
为了测试组件,我们创建了 setup() 辅助,用来把模拟过的(stubbed)回调当作 props 传入,然后使用 () 来渲染组件。这样就可以依据 “是否了回调” 的断言来写独立的测试。
import React, { Component } from 'react' import PropTypes from 'prop-types' import TodoTextInput from './TodoTextInput' class Header extends Component { handleSave(text) { if (text.length !== 0) { this.props.addTodo(text) } } render() { return ( <header className="header"> <h1>todos</h1> <TodoTextInput newTodo={true} onSave={this.handleSave.bind(this)} placeholder="What needs to be done?" /> </header> ) } } Header.propTypes = { addTodo: PropTypes.func.is } export default Header@H__9@上面的组件可以这样来测试:
import React from 'react' import Enzyme, { mount } from 'enzyme' import Adapter from 'enzyme-adapter-react-16'; import Header from '../../components/Header' Enzyme.conure({ adapter: new Adapter() }); function setup() { const props = { addTodo: jest.fn() } const enzymeWrapper = mount(<Header {...props} />) return { props, enzymeWrapper } } describe('components', () => { describe('Header', () => { it('should render self and subcomponents', () => { const { enzymeWrapper } = setup() expect(enzymeWrapper.find('header').hasClass('header')).toBe(true) expect(enzymeWrapper.find('h1').text()).toBe('todos') const todoInputProps = enzymeWrapper.find('TodoTextInput').props() expect(todoInputProps.newTodo).toBe(true) expect(todoInputProps.placeholder).toEqual('What needs to be done?') }) it('should call addTodo if length of text is greater than 0', () => { const { enzymeWrapper, props } = setup() const input = enzymeWrapper.find('TodoTextInput') input.props().onSave('') expect(props.addTodo.mock.calls.length).toBe(0) input.props().onSave('Use R') expect(props.addTodo.mock.calls.length).toBe(1) }) }) })@H__9@测试 connected 组件
如果你使用类似 的库,你可能会使用 ,比如 。可以让你把 R state 注入到常规的 React 组件中。
考虑如下 App 组件
import { connect } from 'react-r' class App extends Component { /* ... */ } export default connect(mapStateToProps)(App)@H__9@在单元测试中,通常会这样导入 App 组件:
import App from './App'@H__9@不过,上面这样导入的是通过 connect() 返回的包装组件,并非 App 组件本身,如果你想测试和 R 的整合,这很容易,通过 包裹它后传入用以单元测试的特殊 store 就可以了。但是有时候我们想测试的其实是不带 R store 的组件的渲染。
为了测试 App 组件本身而不用处理装饰器,我们推荐你导出未装饰的组件:
import { connect } from 'react-r' // 命名导出未连接的组件 (测试用) export class App extends Component { /* ... */ } // 认导出已连接的组件 (app 用) export default connect(mapStateToProps)(App)@H__9@由于认导出的组件依旧是包装过的组件,上面中的导入依旧会生效,无须你更改已有的。不过现在你可以通过下面这样的办法导入未装饰的组件了:
import { App } from './App'@H__9@如果你需要导入二者,可以按下面这样做:
import ConnectedApp, { App } from './App'@H__9@在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
上面的组件可以这样来测试:
import React from 'react' import Enzyme, { mount } from 'enzyme' import Adapter from 'enzyme-adapter-react-16'; import Header from '../../components/Header' Enzyme.conure({ adapter: new Adapter() }); function setup() { const props = { addTodo: jest.fn() } const enzymeWrapper = mount(<Header {...props} />) return { props, enzymeWrapper } } describe('components', () => { describe('Header', () => { it('should render self and subcomponents', () => { const { enzymeWrapper } = setup() expect(enzymeWrapper.find('header').hasClass('header')).toBe(true) expect(enzymeWrapper.find('h1').text()).toBe('todos') const todoInputProps = enzymeWrapper.find('TodoTextInput').props() expect(todoInputProps.newTodo).toBe(true) expect(todoInputProps.placeholder).toEqual('What needs to be done?') }) it('should call addTodo if length of text is greater than 0', () => { const { enzymeWrapper, props } = setup() const input = enzymeWrapper.find('TodoTextInput') input.props().onSave('') expect(props.addTodo.mock.calls.length).toBe(0) input.props().onSave('Use R') expect(props.addTodo.mock.calls.length).toBe(1) }) }) })@H__9@测试 connected 组件
如果你使用类似 的库,你可能会使用 ,比如 。可以让你把 R state 注入到常规的 React 组件中。
考虑如下 App 组件
import { connect } from 'react-r' class App extends Component { /* ... */ } export default connect(mapStateToProps)(App)@H__9@在单元测试中,通常会这样导入 App 组件:
import App from './App'@H__9@不过,上面这样导入的是通过 connect() 返回的包装组件,并非 App 组件本身,如果你想测试和 R 的整合,这很容易,通过 包裹它后传入用以单元测试的特殊 store 就可以了。但是有时候我们想测试的其实是不带 R store 的组件的渲染。
为了测试 App 组件本身而不用处理装饰器,我们推荐你导出未装饰的组件:
import { connect } from 'react-r' // 命名导出未连接的组件 (测试用) export class App extends Component { /* ... */ } // 认导出已连接的组件 (app 用) export default connect(mapStateToProps)(App)@H__9@由于认导出的组件依旧是包装过的组件,上面中的导入依旧会生效,无须你更改已有的。不过现在你可以通过下面这样的办法导入未装饰的组件了:
import { App } from './App'@H__9@如果你需要导入二者,可以按下面这样做:
import ConnectedApp, { App } from './App'@H__9@在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
如果你使用类似 的库,你可能会使用 ,比如 。可以让你把 R state 注入到常规的 React 组件中。
考虑如下 App 组件
import { connect } from 'react-r' class App extends Component { /* ... */ } export default connect(mapStateToProps)(App)@H__9@在单元测试中,通常会这样导入 App 组件:
import App from './App'@H__9@不过,上面这样导入的是通过 connect() 返回的包装组件,并非 App 组件本身,如果你想测试和 R 的整合,这很容易,通过 包裹它后传入用以单元测试的特殊 store 就可以了。但是有时候我们想测试的其实是不带 R store 的组件的渲染。
为了测试 App 组件本身而不用处理装饰器,我们推荐你导出未装饰的组件:
import { connect } from 'react-r' // 命名导出未连接的组件 (测试用) export class App extends Component { /* ... */ } // 认导出已连接的组件 (app 用) export default connect(mapStateToProps)(App)@H__9@由于认导出的组件依旧是包装过的组件,上面中的导入依旧会生效,无须你更改已有的。不过现在你可以通过下面这样的办法导入未装饰的组件了:
import { App } from './App'@H__9@如果你需要导入二者,可以按下面这样做:
import ConnectedApp, { App } from './App'@H__9@在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
在单元测试中,通常会这样导入 App 组件:
import App from './App'@H__9@不过,上面这样导入的是通过 connect() 返回的包装组件,并非 App 组件本身,如果你想测试和 R 的整合,这很容易,通过 包裹它后传入用以单元测试的特殊 store 就可以了。但是有时候我们想测试的其实是不带 R store 的组件的渲染。
为了测试 App 组件本身而不用处理装饰器,我们推荐你导出未装饰的组件:
import { connect } from 'react-r' // 命名导出未连接的组件 (测试用) export class App extends Component { /* ... */ } // 认导出已连接的组件 (app 用) export default connect(mapStateToProps)(App)@H__9@由于认导出的组件依旧是包装过的组件,上面中的导入依旧会生效,无须你更改已有的。不过现在你可以通过下面这样的办法导入未装饰的组件了:
import { App } from './App'@H__9@如果你需要导入二者,可以按下面这样做:
import ConnectedApp, { App } from './App'@H__9@在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
不过,上面这样导入的是通过 connect() 返回的包装组件,并非 App 组件本身,如果你想测试和 R 的整合,这很容易,通过 包裹它后传入用以单元测试的特殊 store 就可以了。但是有时候我们想测试的其实是不带 R store 的组件的渲染。
为了测试 App 组件本身而不用处理装饰器,我们推荐你导出未装饰的组件:
import { connect } from 'react-r' // 命名导出未连接的组件 (测试用) export class App extends Component { /* ... */ } // 认导出已连接的组件 (app 用) export default connect(mapStateToProps)(App)@H__9@由于认导出的组件依旧是包装过的组件,上面中的导入依旧会生效,无须你更改已有的。不过现在你可以通过下面这样的办法导入未装饰的组件了:
import { App } from './App'@H__9@如果你需要导入二者,可以按下面这样做:
import ConnectedApp, { App } from './App'@H__9@在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
由于认导出的组件依旧是包装过的组件,上面中的导入依旧会生效,无须你更改已有的。不过现在你可以通过下面这样的办法导入未装饰的组件了:
import { App } from './App'@H__9@如果你需要导入二者,可以按下面这样做:
import ConnectedApp, { App } from './App'@H__9@在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
如果你需要导入二者,可以按下面这样做:
import ConnectedApp, { App } from './App'@H__9@在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
在 app 中,仍然正常地导入:
import App from './App'@H__9@只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
对中间件的测试
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
示例
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
只在测试中使用命名导出。
混用 ES6 模块和 CommonJS 的注意事项
如果在应用中使用 ES6,但在测试中使用 ES5,Babel 会通过其 机制处理 ES6 的 import 和 CommonJS 的 require ,使得这两种模式能一起使用,但其行为依旧有细微的区别。 如果在认导出的附近另导出,将导致无法认导出 require('./App')。此时,应代以 require('./App').default
中间件包装了 R 中 dispatch 的行为,为了测试中间件的行为,我们需要模拟 dispatch 时的行为。
首先,我们需要创建中间件,下述和 类似
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState) } return next(action) }@H__9@我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
我们需要创造假的 getState,dispatch 和 next ,我们可以使用 jest.fn() 来创建 stubs,你也可以使用 sinon 等测试框架
我们可以像 R 一样来触发
const create = () => { const store = { getState: jest.fn(() => ({})), dispatch: jest.fn(), }; const next = jest.fn() const invoke = (action) => thunk(store)(next)(action) return {store, next, invoke} };@H__9@然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
然后我们在适当的时机通过 getState,dispatch,next来测试中间件。
it('passes through non-function action', () => { const { next, invoke } = create() const action = {type: 'TEST'} invoke(action) expect(next).toHaveBeenCalledWith(action) }) it('calls the function', () => { const { invoke } = create() const fn = jest.fn() invoke(fn) expect(fn).toHaveBeenCalled() }); it('passes dispatch and getState', () => { const { store, invoke } = create() invoke((dispatch, getState) => { dispatch('TEST DISPATCH') getState(); }) expect(store.dispatch).toHaveBeenCalledWith('TEST DISPATCH') expect(store.getState).toHaveBeenCalled() });@H__9@在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
词汇表
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。
在一些情况下,你需要 create 来模拟不同的 getState 和 next 。
: Enzyme 是一种用于 React 测试的 JavaScript 工具,它使得断言、操作以及遍历你的 React 组件的变得更简单。
:React 提供的测试工具,被 Enzyme 使用
: shallow renderer 使你可以实例化组件, 并有效地其 render 的结果, 其渲染深度仅一层, 而非递归地将组件渲染为 DOM。 shallow renderer 对单元测试很有用, 你只要测试某个特定的组件,而不用管它的子组件。这也意味着,更改子组件不会影响到其父组件的测试。如果要测试组件和它所有的子组件,可以用 ,这个会进行完全的 DOM 渲染。