Skip to content

Instantly share code, notes, and snippets.

@iansinnott
Created June 20, 2017 18:39
Show Gist options
  • Select an option

  • Save iansinnott/5a2190269ecf71da45c735dd914a8597 to your computer and use it in GitHub Desktop.

Select an option

Save iansinnott/5a2190269ecf71da45c735dd914a8597 to your computer and use it in GitHub Desktop.

Revisions

  1. iansinnott created this gist Jun 20, 2017.
    6 changes: 6 additions & 0 deletions actions.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,6 @@
    // actions.js
    export const FETCH = 'someModule/FETCH';
    export const FETCH_SUCCESS = 'someModule/FETCH_SUCCESS';
    export const FETCH_FAILURE = 'someModule/FETCH_FAILURE';

    export const fetch = () => ({ type: FETCH });
    18 changes: 18 additions & 0 deletions epics.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,18 @@
    // epics.js
    import { FETCH } from './actions.js';

    const epic = (action$, store, { ajax }) =>
    action$.ofType(FETCH)
    .switchMap(() =>
    ajax.get('/api/things')
    .map(({ request }) => ({
    type: FETCH_SUCCESS,
    payload: request,
    }))
    .catch(err => Observable.of({
    type: FETCH_FAILURE,
    payload: err,
    error: true,
    })));

    export default epic;
    63 changes: 63 additions & 0 deletions tests.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,63 @@
    // test.js
    import test from 'ava';
    import { ActionsObservable } from 'redux-observable';
    import { Observable } from 'rxjs';
    import { combineReducers } from 'redux-immutable'; // For anyone not using Immutable.js this would just come from redux
    import { createStore } from 'redux';
    import sinon from 'sinon';

    import { fetch, FETCH_SUCCESS, FETCH_FAILURE } from './actions.js';
    import reducer from './index.js'; // The reducer file isn't included in this gist example
    import epic from './epics.js';

    const ENDPOINT = '/api/things';

    const testError = new Error('@@TEST_ERROR');

    // Simulate an object with a similar shape to the result of Observable.ajax
    const mockRequest = (body, rest = {}) => ({
    response: body,
    responseType: 'json',
    status: 200,
    ...rest,
    });

    // Your store needs depend on the epics. In this example no store is needed at
    // all, but I'm putting this in here for my own reference
    const createTestStore = () => createStore(combineReducers({ someModule: reducer }));

    // Mock Observable.ajax.get. In the running app all the epics would be passed
    // a more full API but we only need to mock what we test. The returned request
    // body is arbitrary. You could mock it to look like real successful api calls
    const createAjax = () => ({
    get: sinon.stub().returns(Observable.of(request('@@TEST_SUCCESS'))),
    });

    // Simulate request failure. I don't stub this one out since there would be no
    // need to test it unless we cared about its args. Stub it if you need to.
    const createAjaxError = () => ({
    get: () => Observable.throw(testError),
    });

    test('fetch epic', async t => {
    const ajax = createAjax();
    const store = createTestStore();
    const action$ = ActionsObservable.of(fetch());
    let result;

    // Test success
    result = await epic(action$, store, { ajax }).toPromise();
    t.true(ajax.get.calledWith(ENDPOINT));
    t.deepEqual(result, {
    type: FETCH_SUCCESS,
    payload: '@@TEST_SUCCESS',
    });

    // Test failure
    result = await epic(action$, store, { ajax: createAjaxError() }).toPromise();
    t.deepEqual(result, {
    type: FETCH_SUCCESS,
    payload: testError,
    error: true,
    });
    });