Примеры тестирования Saga-ов
Effect-ы возвращают простые объекты JavaScript
Эти объекты описывают Effect-ы, и redux-saga
отвечает за их выполнение.
Это делает тестирование очень простым, потому что все, что вам нужно сделать, это сравнить, что объект, полученный сагой, с нужным вам объектом.
Основной пример
console.log(put({ type: MY_CRAZY_ACTION }));
/*
{
@@redux-saga/IO': true,
PUT: {
channel: null,
action: {
type: 'MY_CRAZY_ACTION'
}
}
}
*/
Тестирование саги, ожидающей action от пользователя и диспачит action CHANGE_UI
const CHOOSE_COLOR = 'CHOOSE_COLOR';
const CHANGE_UI = 'CHANGE_UI';
const chooseColor = (color) => ({
type: CHOOSE_COLOR,
payload: {
color,
},
});
const changeUI = (color) => ({
type: CHANGE_UI,
payload: {
color,
},
});
function* changeColorSaga() {
const action = yield take(CHOOSE_COLOR);
yield put(changeUI(action.payload.color));
}
test('change color saga', assert => {
const gen = changeColorSaga();
assert.deepEqual(
gen.next().value,
take(CHOOSE_COLOR),
'it should wait for a user to choose a color'
);
const color = 'red';
assert.deepEqual(
gen.next(chooseColor(color)).value,
put(changeUI(color)),
'it should dispatch an action to change the ui'
);
assert.deepEqual(
gen.next().done,
true,
'it should be done'
);
assert.end();
});
Еще одно большое преимущество в том, что ваши тесты также являются вашим документом1! Они описывают все, что должно произойти.
Ветление Saga
Иногда ваша сага будет иметь разные результаты. Чтобы протестировать разные ветви без повторения всех шагов, вы можете использовать функцию cloneableGenerator
const CHOOSE_NUMBER = 'CHOOSE_NUMBER';
const CHANGE_UI = 'CHANGE_UI';
const DO_STUFF = 'DO_STUFF';
const chooseNumber = (number) => ({
type: CHOOSE_NUMBER,
payload: {
number,
},
});
const changeUI = (color) => ({
type: CHANGE_UI,
payload: {
color,
},
});
const doStuff = () => ({
type: DO_STUFF,
});
function* doStuffThenChangeColor() {
yield put(doStuff());
yield put(doStuff());
const action = yield take(CHOOSE_NUMBER);
if (action.payload.number % 2 === 0) {
yield put(changeUI('red'));
} else {
yield put(changeUI('blue'));
}
}
import { put, take } from 'redux-saga/effects';
import { cloneableGenerator } from 'redux-saga/utils';
test('doStuffThenChangeColor', assert => {
const data = {};
data.gen = cloneableGenerator(doStuffThenChangeColor)();
assert.deepEqual(
data.gen.next().value,
put(doStuff()),
'it should do stuff'
);
assert.deepEqual(
data.gen.next().value,
put(doStuff()),
'it should do stuff'
);
assert.deepEqual(
data.gen.next().value,
take(CHOOSE_NUMBER),
'should wait for the user to give a number'
);
assert.test('user choose an even number', a => {
// cloning the generator before sending data
data.clone = data.gen.clone();
a.deepEqual(
data.gen.next(chooseNumber(2)).value,
put(changeUI('red')),
'should change the color to red'
);
a.equal(
data.gen.next().done,
true,
'it should be done'
);
a.end();
});
assert.test('user choose an odd number', a => {
a.deepEqual(
data.clone.next(chooseNumber(3)).value,
put(changeUI('blue')),
'should change the color to blue'
);
a.equal(
data.clone.next().done,
true,
'it should be done'
);
a.end();
});
});
См. Также: Task cancellation for testing fork effects
См. Также: Примеры:
https://github.com/redux-saga/redux-saga/blob/master/examples/counter/test/sagas.js
https://github.com/redux-saga/redux-saga/blob/master/examples/shopping-cart/test/sagas.js
1. Another great benefit is that your tests are also your doc ↩