https://github.com/vangelov/saganario
A compact way to unit test redux sagas
https://github.com/vangelov/saganario
generators redux redux-saga
Last synced: 4 months ago
JSON representation
A compact way to unit test redux sagas
- Host: GitHub
- URL: https://github.com/vangelov/saganario
- Owner: vangelov
- License: mit
- Created: 2017-03-19T20:20:02.000Z (over 9 years ago)
- Default Branch: master
- Last Pushed: 2017-05-15T05:55:57.000Z (about 9 years ago)
- Last Synced: 2026-02-15T01:28:39.281Z (4 months ago)
- Topics: generators, redux, redux-saga
- Language: JavaScript
- Homepage:
- Size: 23.4 KB
- Stars: 8
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# 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:
```javascript
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:
```javascript
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:
```
::= [
expect1,
expect2,
...
expectN,
ifGiven(value1), ,
ifGiven(value2), ,
...
ifGiven(value3),
]
```
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:
```javascript
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:
```javascript
function *saga() {
yield 'value1';
yield 'value2';
}
```
Test:
```javascript
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 (:3)`.
If you need to use the information from the message you can catch the error and get it in this way:
```javascript
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:
```javascript
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:
```javascript
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:
```javascript
function *saga() {
yield 'value1';
yield 'value2';
}
```
Test:
```javascript
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()`, otherwise it's thrown as an exception in the generator using `generator.throw()`.
## 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`.