expectEpic
import Mock = jest.Mock;
import {expectEpic} from './expect-epic';
import {getEpic} from './test-epic';
describe('expectEpic', () => {
const actions = {
FETCH_FOO: 'FETCH_FOO',
FETCH_FOO_FULFILLED: 'FETCH_FOO_FULFILLED',
FETCH_FOO_CANCELLED: 'FETCH_FOO_CANCELLED',
FETCH_FOO_REJECTED: 'FETCH_FOO_REJECTED'
};
it('calls the correct API', () => {
const url = 'some-url';
const responseData = {id: 123, name: 'Bilbo'};
expectEpic(getEpic, {
action: {
marbles: '(a|)',
values: {
a: { type: actions.FETCH_FOO, payload: {id: 123} }
}
},
expected: {
marbles: '-a|',
values: {
a: { type: actions.FETCH_FOO_FULFILLED, payload: responseData }
}
},
response: {
marbles: '-a|',
values: { a: responseData }
},
callAjaxArgs: [url, {id: 123}]
});
});
it('handles errors correctly', () => {
const url = 'some-url';
const responseData = {id: 123, name: 'Bilbo'};
expectEpic(getEpic, {
action: {
marbles: '(a|)',
values: {
a: {type: actions.FETCH_FOO, payload: {id: 123}}
}
},
expected: {
marbles: '-(a|)',
values: {
a: {type: actions.FETCH_FOO_REJECTED, error: true}
}
},
response: {
marbles: '-#',
values: null,
error: {xhr: {responseData}}
},
callAjaxArgs: [url, {id: 123}]
});
});
it('handles cancellation correctly', () => {
const url = 'some-url';
expectEpic(getEpic, {
action: {
marbles: 'ab|',
values: {
a: { type: actions.FETCH_FOO, payload: { id: 123 } },
b: { type: actions.FETCH_FOO_CANCELLED }
}
},
expected: {
marbles: '--|',
},
response: {
marbles: '-a|',
values: {
a: { message: 'SHOULD_NOT_EMIT_THIS' }
}
},
callAjaxArgs: [url, {id: 123}]
});
});
});
import Mock = jest.Mock;
import {TestScheduler, Observable} from 'rxjs';
import {ActionsObservable, Epic} from 'redux-observable';
import {MiddlewareAPI} from 'redux';
import configureMockStore from 'redux-mock-store';
import {ActionWithPayload, SimpleAction} from '../../interfaces/actions';
import {Dict} from '../../interfaces/index';
interface TestObservableData <T> {
readonly marbles: string;
readonly values?: Dict<T>;
readonly error?: any;
}
const mockStore = configureMockStore();
export const expectEpic = (getEpic: (getAjax: (url: string, dataToSend: any) => Observable<any>) => Epic<any, any>,
options: {
action: TestObservableData<SimpleAction | ActionWithPayload<any>>,
expected: TestObservableData<SimpleAction | ActionWithPayload<any>>,
response: TestObservableData<any>,
store?: MiddlewareAPI<any>
callAjaxArgs: ReadonlyArray<any>
}) => {
const {action, expected, response, callAjaxArgs} = options;
const store = options.store || mockStore();
const testScheduler = new TestScheduler((actual: any, expectedData: any) => {
return expect(actual).toEqual(expectedData);
});
const action$: ActionsObservable<{}> = new ActionsObservable<{}>(
testScheduler.createHotObservable<{}>(action.marbles, action.values, action.error)
);
const response$ = testScheduler.createColdObservable(response.marbles, response.values, response.error);
const getAjax = jest.fn(() => response$);
const callsOfGetAjax = (getAjax as Mock<any>).mock.calls;
const responseMarbles = '^!';
const epic = getEpic(getAjax);
const test$ = epic(action$, store);
testScheduler.expectObservable(test$).toBe(expected.marbles, expected.values);
testScheduler.flush();
expect(callsOfGetAjax.length).toEqual(1);
expect(callsOfGetAjax[0]).toEqual(callAjaxArgs);
testScheduler.expectSubscriptions(response$.subscriptions).toBe(responseMarbles);
};
import {Observable} from 'rxjs/Observable';
import {Epic} from 'redux-observable';
export const getEpic = (getAjax: (url: string, dataToSend: any) => Observable<any>): Epic<{payload: {}}, {}> => {
const actions = {
FETCH_FOO: 'FETCH_FOO',
FETCH_FOO_FULFILLED: 'FETCH_FOO_FULFILLED',
FETCH_FOO_CANCELLED: 'FETCH_FOO_CANCELLED',
FETCH_FOO_REJECTED: 'FETCH_FOO_REJECTED'
};
return (action$, store) => {
return action$.ofType(actions.FETCH_FOO)
.switchMap(action => {
const dataToSend = action.payload;
return getAjax('some-url', dataToSend)
.takeUntil(action$.ofType(actions.FETCH_FOO_CANCELLED))
.map(payload => ({
type: actions.FETCH_FOO_FULFILLED,
payload: payload
}))
.catch(error => Observable.of({
type: actions.FETCH_FOO_REJECTED,
payload: error.xhr.response,
error: true
}));
});
};
};