对redux-saga观察者进行单元测试有什么意义?

时间:2017-03-06 11:34:27

标签: unit-testing redux-saga

为了100%覆盖我的Saga文件,我正在研究如何测试观察者。

我一直在谷歌搜索,有几个答案如何测试观察者。也就是说,saga会执行takeEverytakeLatest

但是,所有测试方法似乎都基本上复制了实现。那么编写测试的重点是什么?

示例:

// saga.js

import { delay } from 'redux-saga'
import { takeEvery, call, put } from 'redux-saga/effects'
import { FETCH_RESULTS, FETCH_COMPLETE } from './actions'

import mockResults from './tests/results.mock'

export function* fetchResults () {
  yield call(delay, 1000)
  yield put({ type: FETCH_COMPLETE, mockResults })
}

export function* watchFetchResults () {
  yield takeEvery(FETCH_RESULTS, fetchResults)
}

测试方法1:

import { takeEvery } from 'redux-saga/effects'
import { watchFetchResults, fetchResults } from '../sagas'
import { FETCH_RESULTS } from '../actions'

describe('watchFetchResults()', () => {
    const gen = watchFetchResults()
    // exactly the same as implementation
    const expected = takeEvery(FETCH_RESULTS, fetchResults)
    const actual = gen.next().value

    it('Should fire on FETCH_RESULTS', () => {
      expect(actual).toEqual(expected)
    })
  })

测试方法2:使用助手,如Redux Saga Test Plan
这是一种不同的写作方式,但我们的表现与实现基本相同。

import testSaga from 'redux-saga-test-plan'
import { watchFetchResults, fetchResults } from '../sagas'
import { FETCH_RESULTS } from '../actions'

it('fire on FETCH_RESULTS', () => {
  testSaga(watchFetchResults)
    .next()
    .takeEvery(FETCH_RESULTS, fetchResults)
    .finish()
    .isDone()
})

相反,我想知道watchFestchResults是否占用每个FETCH_RESULTS。或者甚至只有它发射takeEvery()。无论如何跟进。

或者这真的是这样做的吗?

2 个答案:

答案 0 :(得分:8)

听起来测试它们的目的是达到100%的测试覆盖率。

有些事情你可以进行单元测试,但是如果你应该这样做是值得怀疑的。

在我看来,这种情况可能是一个更好的“整合”的候选人。测试。不仅仅测试单个方法的东西,而是几个方法作为一个整体协同工作的东西。也许你可以调用一个动作来触发使用你的传奇的减速器,然后检查商店的结果变化?这比单独测试传奇更有意义。

答案 1 :(得分:4)

我同意John Meyer的answer,它更适合集成测试而不是单元测试。这个issue是基于投票的GitHub中最受欢迎的。我建议你阅读它。

其中一个建议是使用由问题的开启者创建的redux-saga-tester包。它有助于创建初始状态,启动saga帮助程序(takeEvery,takeLatest),saga正在侦听的调度操作,观察状态,检索操作历史并监听要发生的特定操作。

我将其与axios-mock-adapter一起使用,但代码库中有几个使用nock的示例。

佐贺

import { takeLatest, call, put } from 'redux-saga/effects';
import { actions, types } from 'modules/review/reducer';
import * as api from 'api';

export function* requestReviews({ locale }) {
  const uri = `/reviews?filter[where][locale]=${locale}`;
  const response = yield call(api.get, uri);
  yield put(actions.receiveReviews(locale, response.data[0].services));
}

// Saga Helper
export default function* watchRequestReviews() {
  yield takeLatest(types.REVIEWS_REQUEST, requestReviews);
}

使用Jest的测试示例

import { takeLatest } from 'redux-saga/effects';
import { types } from 'modules/review/reducer';
import SagaTester from 'redux-saga-tester';
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';

import watchRequestReviews, { requestReviews } from '../reviews';

const mockAxios = new MockAdapter(axios);

describe('(Saga) Reviews', () => {
  afterEach(() => {
    mockAxios.reset();
  });

  it('should received reviews', async () => {
    const services = [
      {
        title: 'Title',
        description: 'Description',
      },
    ];
    const responseData = [{
      id: '595bdb2204b1aa3a7b737165',
      services,
    }];

    mockAxios.onGet('/api/reviews?filter[where][locale]=en').reply(200, responseData);

    // Start up the saga tester
    const sagaTester = new SagaTester({ initialState: { reviews: [] } });

    sagaTester.start(watchRequestReviews);

    // Dispatch the event to start the saga
    sagaTester.dispatch({ type: types.REVIEWS_REQUEST, locale: 'en' });

    // Hook into the success action
    await sagaTester.waitFor(types.REVIEWS_RECEIVE);

    expect(sagaTester.getLatestCalledAction()).toEqual({
      type: types.REVIEWS_RECEIVE,
      payload: { en: services },
    });
  });
});