💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 编写测试 因为你写的大部分 Redux 代码都是些函数,而且大部分是纯函数,所以很好测,不需要 mock。 ### 设置 我们建议用 [Mocha](http://mochajs.org/) 作为测试引擎。 注意因为是在 node 环境下运行,所以你不能访问 DOM。 ~~~ npm install --save-dev mocha ~~~ 想结合 [Babel](http://babeljs.io) 使用的话,在 `package.json` 的 `scripts` 里加入这一段: ~~~ { ... "scripts": { ... "test": "mocha --compilers js:babel/register --recursive", "test:watch": "npm test -- --watch", }, ... } ~~~ 然后运行 `npm test` 就能单次运行了,或者也可以使用 `npm run test:watch` 在每次有文件改变时自动执行测试。 ### Action Creators Redux 里的 action creators 是会返回普通对象的函数。在测试 action creators 的时候我们想要测试不仅是调用了正确的 action creator,还有是否返回了正确的 action。 #### 示例 ~~~ export function addTodo(text) { return { type: 'ADD_TODO', text }; } ~~~ 可以这么测: ~~~ import expect from 'expect'; import * as actions from '../../actions/TodoActions'; import * as types from '../../constants/ActionTypes'; describe('actions', () => { it('should create an action to add a todo', () => { const text = 'Finish docs'; const expectedAction = { type: types.ADD_TODO, text }; expect(actions.addTodo(text)).toEqual(expectedAction); }); } ~~~ ### Reducers Reducer 应该是把 action 应用到之前的 state,并返回新的 state。测试起来是下面这样的。 #### 示例 ~~~ import { ADD_TODO } from '../constants/ActionTypes'; const initialState = [{ text: 'Use Redux', completed: false, id: 0 }]; export default function todos(state = initialState, action) { switch (action.type) { case ADD_TODO: return [{ id: (state.length === 0) ? 0 : state[0].id + 1, completed: false, text: action.text }, ...state]; default: return state; } } ~~~ 可以这么测: ~~~ import expect from 'expect'; import reducer from '../../reducers/todos'; import * as types from '../../constants/ActionTypes'; describe('todos reducer', () => { it('should return the initial state', () => { expect( reducer(undefined, {}) ).toEqual([{ text: 'Use Redux', completed: false, id: 0 }]); }); it('should handle ADD_TODO', () => { expect( reducer([], { type: types.ADD_TODO, text: 'Run the tests' }) ).toEqual([{ text: 'Run the tests', completed: false, id: 0 }]); expect( reducer([{ text: 'Use Redux', completed: false, id: 0 }], { type: types.ADD_TODO, text: 'Run the tests' }) ).toEqual([{ text: 'Run the tests', completed: false, id: 1 }, { text: 'Use Redux', completed: false, id: 0 }]); }); ~~~ ### Components React components 有一点好,就是他们一般都很小而且依赖于他们的 props。所以很好测。 要测 components 我们要建一个叫 `setup()` 的辅助方法,用来把模拟过的(stubbed)回调函数当作 props 来传入,然后使用 [React 浅渲染](https://facebook.github.io/react/docs/test-utils.html#shallow-rendering) 来渲染组件。这样就可以通过做 “是否调用了回调函数” 这样的断言来写独立的测试。 #### 示例 ~~~ import React, { PropTypes, Component } from 'react'; import TodoTextInput from './TodoTextInput'; class Header extends Component { handleSave(text) { if (text.length !== 0) { this.props.addTodo(text); } } render() { return ( <header 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.isRequired }; export default Header; ~~~ 可以这么测: ~~~ import expect from 'expect'; import jsdomReact from '../jsdomReact'; import React from 'react/addons'; import Header from '../../components/Header'; import TodoTextInput from '../../components/TodoTextInput'; const { TestUtils } = React.addons; function setup() { let props = { addTodo: expect.createSpy() }; let renderer = TestUtils.createRenderer(); renderer.render(<Header {...props} />); let output = renderer.getRenderOutput(); return { props: props, output: output, renderer: renderer }; } describe('components', () => { jsdomReact(); describe('Header', () => { it('should render correctly', () => { const { output } = setup(); expect(output.type).toBe('header'); expect(output.props.className).toBe('header'); let [h1, input] = output.props.children; expect(h1.type).toBe('h1'); expect(h1.props.children).toBe('todos'); expect(input.type).toBe(TodoTextInput); expect(input.props.newTodo).toBe(true); expect(input.props.placeholder).toBe('What needs to be done?'); }); it('should call call addTodo if length of text is greater than 0', () => { const { output, props } = setup(); let input = output.props.children[1]; input.props.onSave(''); expect(props.addTodo.calls.length).toBe(0); input.props.onSave('Use Redux'); expect(props.addTodo.calls.length).toBe(1); }); }); }); ~~~ #### `setState()` 异常修复 浅渲染目前的问题是 [如果调用 `setState` 便抛异常](https://github.com/facebook/react/issues/4019). React 貌似想要的是,如果想要使用 `setState`,DOM 就一定要存在(但测试运行在 node 环境下,是没有 DOM 的)。要解决这个问题,我们用了 jsdom,为了在 DOM 无效的时候,React 也不抛异常。按下面方法设置它: ~~~ npm install --save-dev jsdom mocha-jsdom ~~~ 然后添加 `jsdomReact()` 帮助函数,是这样的: ~~~ import ExecutionEnvironment from 'react/lib/ExecutionEnvironment'; import jsdom from 'mocha-jsdom'; export default function jsdomReact() { jsdom(); ExecutionEnvironment.canUseDOM = true; } ~~~ 要在运行任何的 component 测试之前调用。注意这么做不优雅,等以后 [facebook/react#4019](https://github.com/facebook/react/issues/4019) 解决了之后,这段代码就可以删除了。 ### 词汇表 - [React Test Utils](http://facebook.github.io/react/docs/test-utils.html): 跟 React 一块来的测试小助手。 - [jsdom](https://github.com/tmpvar/jsdom): 一个 JavaScript 的内建 DOM 。Jsdom 允许没浏览器的时候也能跑测试。 - [浅渲染(shallow renderer)](http://facebook.github.io/react/docs/test-utils.html#shallow-rendering): 浅渲染的中心思想是,初始化一个 component 然后得到它的`渲染`方法作为结果,比起渲染成 DOM 那么深的只有一级那么深。浅渲染的结果是一个 [ReactElement](https://facebook.github.io/react/docs/glossary.html#react-elements) ,意味着可以访问它的 children, props 还能测试是否工作正常。