barbiturat
4/22/2017 - 2:27 PM

expectEpic

expectEpic

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
          }));
      });
  };
};
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 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}]
    });
  });

});