saganario v1.0.0
saganario
When I first began writing unit tests for sagas using a standard framework such as mocha
, I noticed
that there was too much boilerplate that had to be written and it was tedious to test different code paths. Another problem was tests checked every yielded value and its order which made them too dependent on the saga implementation. I looked into some of the libraries created specifically for testing sagas but they didn't seem much better.
Saganario
tries to provide a compact and powerful way to unit test redux sagas. It can be used with any testing framework.
Example
Let's say we have the following saga:
function* saga(x, y) {
try {
const action = yield take('SOME_ACTION');
yield put({ type: 'OTHER_ACTION', value: x + y + action.z });
} catch (e) {
yield put({ type: 'ERROR' });
}
}
Here's a sample test for it:
import { prepareTest, expectNext, ifGiven } from 'saganario';
const test = prepareTest(saga, 1, 2, [
expectNext(take('SOME_ACTION')),
ifGiven({ type: 'SOME_ACTION', value: 2 }), [
expectNext(put({ type: 'OTHER_ACTION', value: 3 })),
],
ifGiven(new Error()), [
expectNext(put({ type: 'ERROR' })),
],
]);
The code above tests two different paths depending on whether there's an error.
Saganario
tests are made of nested arrays and look a bit like Lisp code.
Details
The tests can be recursively defined as follows:
<saganarioTest> ::= [
expect1,
expect2,
...
expectN,
ifGiven(value1), <saganarioTest>,
ifGiven(value2), <saganarioTest>,
...
ifGiven(value3), <saganarioTest>
]
Essentially they represent a tree in which each node is a sequence of expect
calls and each child node is yet another sequence of expect
calls to which we transition by giving the argument of the ifGiven
function to the saga. Saganario
will run and check each path in the tree from the root to any of the leaves.
Running tests
The prepareTest
function you saw earlier creates a function that you can execute and in case of an unmet expectation it throws an error. prepareTest
takes the saga as the first argument, then any arguments the saga itself takes and then the test scenario.
For example you can use saganario
with mocha
like so:
describe('saga', () => {
it('Does not throw in this scenario', () => {
const test = prepareTest(saga, 1, 2, [
...
]);
expect(test).to.not.throw();
});
});
Errors
Whenever an expected value is not met saganario
throws an error whose message contains: the expected value, the actual value and also the line in your test which caused the error.
Example saga:
function *saga() {
yield 'value1';
yield 'value2';
}
Test:
1. const test = prepareTest(saga, [
2. expectNext('value1');
3. expectNext('otherValue'); // on this line the expectation is not met
4. ]);
An error will be thrown with the following message [expected]: value1; [actual]: otherValue (<path-to-your-file.js>:3)
.
If you need to use the information from the message you can catch the error and get it in this way:
try {
test()
} catch(error) {
const { expected, actual, line } = error.saganario;
// ...
}
Expecting values
There are several expectation types that you can use:
expectNext
This is the most common type of expectation. It just checks if the next yielded value from the saga is what it's expected to be. You already saw examples of how to use it.
expectEventually
Often you don't care for all the values the saga yields, but only that it eventually emits a certain value. Take the following saga as an example:
function *saga() {
const value = yield 'start';
if (value > 0) {
yield 'step1';
yield 'step2';
yield 'step3';
yield 'end';
}
yield 'error';
}
You only care that it yields 'start'
at the beginning and than, at some point later, it emits 'end'
. You can write the following test:
const test = prepareTest(saga, [
expectNext('start');
ifGiven(100), [
expectEventually('end'),
],
ifGiven(-1), [
expectNext('error'),
]
]);
Note: If the expected value is not reached after 100 iterations of the generator an error will be thrown.
expectEnd
This type of expectation is used when you want to check that after the expected value is emitted, the generator ends.
Example saga:
function *saga() {
yield 'value1';
yield 'value2';
}
Test:
const test = prepareTest(saga, [
expectNext('value1');
expectEnd('value2');
]);
Note: An error will be thrown if the generator is not done when expectEnd
is called.
Giving values
The only function you use for this is ifGiven
. If the argument you set is not an instance of Error
the value is given to the generator by using generator.next(<value>)
, otherwise it's thrown as an exception in the generator using generator.throw(<value>)
.
Installation
npm install --save saganario
Example
In the /examples
folder you will find several tests for sagas of different complexities. You can run them with: npm run examples
.
Test
To run the tests execute: npm test
.
7 years ago