Skip to content

Instantly share code, notes, and snippets.

@iansinnott
Created June 20, 2017 18:39
Show Gist options
  • Save iansinnott/5a2190269ecf71da45c735dd914a8597 to your computer and use it in GitHub Desktop.
Save iansinnott/5a2190269ecf71da45c735dd914a8597 to your computer and use it in GitHub Desktop.
epic testing
// 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 });
// 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;
// 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,
});
});
@iansinnott
Copy link
Author

For epics where you expect more than one action to come through the stream toArray() could be used before toPromise() and deep equality testing should still work.

@BerkeleyTrue
Copy link

Why are you using async/await? Why not just use observables?

@iansinnott
Copy link
Author

@BerkeleyTrue ah, good question. I found the ava support for Rx observables to be a bit lacking so I went with this approach. Maybe I was using it wrong.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment