Redux 編寫測(cè)試

2021-09-16 10:07 更新

編寫測(cè)試

因?yàn)槟銓懙拇蟛糠?Redux 代碼都是些函數(shù),而且大部分是純函數(shù),所以很好測(cè),不需要 mock。

設(shè)置

我們建議用 Mocha 作為測(cè)試引擎。
注意因?yàn)槭窃?node 環(huán)境下運(yùn)行,所以你不能訪問 DOM。

npm install --save-dev mocha

想結(jié)合 Babel 使用的話,在 package.jsonscripts 里加入這一段:

{
  ...
  "scripts": {
    ...
    "test": "mocha --compilers js:babel/register --recursive",
    "test:watch": "npm test -- --watch",
  },
  ...
}

然后運(yùn)行 npm test 就能單次運(yùn)行了,或者也可以使用 npm run test:watch 在每次有文件改變時(shí)自動(dòng)執(zhí)行測(cè)試。

Action Creators

Redux 里的 action creators 是會(huì)返回普通對(duì)象的函數(shù)。在測(cè)試 action creators 的時(shí)候我們想要測(cè)試不僅是調(diào)用了正確的 action creator,還有是否返回了正確的 action。

示例

export function addTodo(text) {
  return {
    type: 'ADD_TODO',
    text
  };
}

可以這么測(cè):

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 應(yīng)該是把 action 應(yīng)用到之前的 state,并返回新的 state。測(cè)試起來是下面這樣的。

示例

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;
  }
}

可以這么測(cè):

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 有一點(diǎn)好,就是他們一般都很小而且依賴于他們的 props。所以很好測(cè)。

要測(cè) components 我們要建一個(gè)叫 setup() 的輔助方法,用來把模擬過的(stubbed)回調(diào)函數(shù)當(dāng)作 props 來傳入,然后使用 React 淺渲染 來渲染組件。這樣就可以通過做 “是否調(diào)用了回調(diào)函數(shù)” 這樣的斷言來寫?yīng)毩⒌臏y(cè)試。

示例

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;

可以這么測(cè):

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() 異常修復(fù)

淺渲染目前的問題是 如果調(diào)用 setState 便拋異常. React 貌似想要的是,如果想要使用 setState,DOM 就一定要存在(但測(cè)試運(yùn)行在 node 環(huán)境下,是沒有 DOM 的)。要解決這個(gè)問題,我們用了 jsdom,為了在 DOM 無效的時(shí)候,React 也不拋異常。按下面方法設(shè)置它:

npm install --save-dev jsdom mocha-jsdom

然后添加 jsdomReact() 幫助函數(shù),是這樣的:

import ExecutionEnvironment from 'react/lib/ExecutionEnvironment';
import jsdom from 'mocha-jsdom';

export default function jsdomReact() {
  jsdom();
  ExecutionEnvironment.canUseDOM = true;
}

要在運(yùn)行任何的 component 測(cè)試之前調(diào)用。注意這么做不優(yōu)雅,等以后 facebook/react#4019 解決了之后,這段代碼就可以刪除了。

詞匯表

  • React Test Utils: 跟 React 一塊來的測(cè)試小助手。

  • jsdom: 一個(gè) JavaScript 的內(nèi)建 DOM 。Jsdom 允許沒瀏覽器的時(shí)候也能跑測(cè)試。

  • 淺渲染(shallow renderer): 淺渲染的中心思想是,初始化一個(gè) component 然后得到它的渲染方法作為結(jié)果,比起渲染成 DOM 那么深的只有一級(jí)那么深。淺渲染的結(jié)果是一個(gè) ReactElement ,意味著可以訪問它的 children, props 還能測(cè)試是否工作正常。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)