对 redux-saga 观察者进行单元测试有什么意义?
What is the point of unit testing redux-saga watchers?
为了 100% 覆盖我的 Saga 文件,我正在研究如何测试观察者。
我一直在谷歌搜索,关于如何测试观察者有几个答案。也就是说,执行 takeEvery
或 takeLatest
.
的传奇
不过,所有的测试方法似乎基本上都是照搬实现。那么如果都一样写一个测试还有什么意义呢?
示例:
// 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)
}
测试方法一:
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)
})
})
测试方法二:有帮手,比如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()
时。不管后续如何。
或者真的是这样吗?
听起来测试它们的目的是为了达到 100% 的测试覆盖率。
有些东西可以进行单元测试,但是否应该进行单元测试值得商榷。
在我看来,这种情况可能更适合 'integration' 测试。不是简单地测试单个方法,而是测试几种方法如何作为一个整体一起工作的东西。也许你可以调用一个动作来触发一个使用你的 saga 的 reducer,然后检查 store 中的结果变化?这比单独测试 saga 更有意义。
我同意 John Meyer 的观点 that this is better suited for the integration test than for the unit test. This issue 是 GitHub 中最受欢迎的(基于投票)。我会推荐阅读它。
其中一个建议是使用问题的开启者创建的 redux-saga-tester 包。它有助于创建初始状态、启动 saga 助手(takeEvery、takeLatest)、调度 saga 正在侦听的动作、观察状态、检索动作历史并侦听特定动作的发生。
我在 axios-mock-adapter, but there are several examples in the codebase using 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 },
});
});
});
为了 100% 覆盖我的 Saga 文件,我正在研究如何测试观察者。
我一直在谷歌搜索,关于如何测试观察者有几个答案。也就是说,执行 takeEvery
或 takeLatest
.
不过,所有的测试方法似乎基本上都是照搬实现。那么如果都一样写一个测试还有什么意义呢?
示例:
// 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)
}
测试方法一:
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)
})
})
测试方法二:有帮手,比如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()
时。不管后续如何。
或者真的是这样吗?
听起来测试它们的目的是为了达到 100% 的测试覆盖率。
有些东西可以进行单元测试,但是否应该进行单元测试值得商榷。
在我看来,这种情况可能更适合 'integration' 测试。不是简单地测试单个方法,而是测试几种方法如何作为一个整体一起工作的东西。也许你可以调用一个动作来触发一个使用你的 saga 的 reducer,然后检查 store 中的结果变化?这比单独测试 saga 更有意义。
我同意 John Meyer 的观点
其中一个建议是使用问题的开启者创建的 redux-saga-tester 包。它有助于创建初始状态、启动 saga 助手(takeEvery、takeLatest)、调度 saga 正在侦听的动作、观察状态、检索动作历史并侦听特定动作的发生。
我在 axios-mock-adapter, but there are several examples in the codebase using 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 },
});
});
});