1.3.0 • Published 5 years ago

saga-guide v1.3.0

Weekly downloads
246
License
MIT
Repository
github
Last release
5 years ago

saga-guide

Easy to use, framework agnostic saga testing utility. It provides you with everything necessary to test your saga by wrapping it in a guided execution environment, and then track which actions was executed or error was thrown.

Getting started

Instalation

npm install --save-dev saga-guide

Importing to your application

import sagaGuide from 'saga-guide';
// OR
const sagaGuide = require('saga-guide');

API reference

sagaGuide(saga: Function, options?: Object): guidedSaga

Creates guided saga instance. 1. saga - a saga you want to guide 2. options - an optional object of saga execution options. It should match options list of runSaga util of redux-saga. However, there is a single saga-guide specific option: * state : any - state which should be used for select effect.

guidedSaga.run(...args: Array<any>): void

Runs guided saga with passed arguments.

function* saga(arg1, arg2) {...}
const guidedSaga = sagaGuide(saga);
guidedSaga.run('arg1 value', 'arg2 value');

You might run your guided saga as many times as you need. Before each run caught error and dispatchState will be reset.

guidedSaga.wasActionDispatched(action: Object): boolean

Returns true, if during last run was dispatched exactly the same action with exactly the same params (compared with deep-equal) as the passed one.

const action = actionCreator(params);
expect(guidedSaga.wasActionDispatched(action))
    .toBeTruthy();

guidedSaga.getAllDispatchedActionsByType(type: string): Array<Object>

Returns list of all dispatched actions during last run by passed action type. It might be useful if you have to check how many times some particular action was called.

expect(guidedSaga.getAllDispatchedActionsByType('actionType'))
    .toHaveLength(3);

guidedSaga.getDispatchStack(): Array<Object>

Returns list of all dispatched actions during last run in order they were dispatched. Could be used to debug your tests by looking on an actual dispatch stack.

expect(guidedSaga.getDispatchStack())
    .toHaveLength(3);

guidedSaga.getResult(): any

Returns a result that was returned by guided saga during the last run if present.

expect(guidedSaga.getResult())
  .toEqual('Expected return result');

guidedSaga.getError(): ?Error

Returns an error that was thrown during the last run if present.

expect(guidedSaga.getError())
    .toBe(saga.UnauthorisedException);

// Or
expect(!!guidedSaga.getError())
    .toBeTruthy();
    
// Or
expect(guidedSaga.getError())
    .toMatchObject({ message: 'error message' });

guidedSaga.setState(state: any): void

Allows you to set state which will be used to resolve select effect. (See Resolving state section)

guidedSaga.setState({ key: 'value' });

Resolving state

If you are using select effect in your sagas, then you would like to mock your redux state for testing. For this you have 2 possibilities:

  • Define state with getState option during initialization
sagaGuide(saga, {
    getState: () => ({ key: 'value' }),
});
  • Define state with guidedSaga state management possibilities
const guidedSaga = sagaGuide(saga, {
    state: { key: 'value' }, // This is optional
});
...
guidedSaga.setState({ key: 'new value' });

Custom expect matchers

Note: Currently we are supporting only Jest custom matchers

toDispatchAction

Allows you to check whether some specific action was dispatched or not.

expect(guidedSaga).toDispatchAction(action);
// Equals to
expect(guidedSaga.wasActionDispatched(action)).toBeTruthy();

However, in case of wrong assertion toDispatchAction matcher will tell you which actions was dispatched during the last run, so it will be easier to debug your test this way.

toDispatchActionType

Allows you to check if an action with specified type was dispatched at least once during last run. The difference with toDispatchAction matcher is that toDispatchActionType will check only type without any additional payload.

This could be useful for .not assertions like:

expect(guidedSaga).toDispatchActionType(actionTypes.type);
expect(guidedSaga).not.toDispatchActionType(actionTypes.type);

Or you can pass an action instead of type. In this case matcher will automatically get passed action's type for an assertion:

const action = { type: actionTypes.type };

expect(guidedSaga).toDispatchActionType(action);
expect(guidedSaga).not.toDispatchActionType(action);

Example

Consider having next saga:

import { put, select } from 'redux-saga/effects';
import api from '../api';
import actions from '../actions';

export default function* saga(arg1) {
    const storedData = yield select(state => state.data);

    try {
        const loadedData = yield api.loadSomeData(arg1, storedData);
        yield put(actions.success(loadedData));
    } catch (e) {
        yield put(actions.error(e.message));
        throw e;
    }
}

Our test will be as simple as this:

import sagaGuide from 'saga-guide';
import api from '../api';
import actions from '../actions';
import saga from './saga';

const arg1 = 'arg1 value';
const state = { data: 'some data' };
const responseData = 'resonse data';

const guidedSaga = sagaGuide(saga, { state });

beforeEach(() => {
    jest.spyOn(api, 'loadSomeData').mockReturnValue(responseData);
    guidedSaga.run(arg1);
});

test('should load data from api with correct params', () => {
    expect(api.loadSomeData)
        .toHaveBeenCalledWith(arg1, state.data);
});

test('should dispatch success action with data loaded from api', () => {
    expect(guidedSaga)
        .toDispatchAction(actions.success(responseData));
});

describe('error flow', () => {
    const error = new Error('error message');

    beforeEach(() => {
        api.loadSomeData.mockImplementation(() => { throw error; });
        guidedSaga.run(arg1);
    });
    
    test('should dispatch error action with error message thrown', () => {
        expect(guidedSaga)
              .toDispatchAction(actions.error(error.message));
    });
    
    test('should throw error further', () => {
        expect(guidedSaga.getError())
            .toBe(error);
    });
});
1.3.0

5 years ago

1.2.0

5 years ago

1.1.1

5 years ago

1.1.0

5 years ago

1.0.7

5 years ago

1.0.6

5 years ago

1.0.5

5 years ago

1.0.4

5 years ago

1.0.3

5 years ago

1.0.2

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago