Skip to content
This repository has been archived by the owner on Feb 19, 2023. It is now read-only.

Commit

Permalink
feat(#91): added test cases for HOC implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Andreas Gasser committed Mar 29, 2019
1 parent d50fa52 commit 221b402
Show file tree
Hide file tree
Showing 2 changed files with 284 additions and 47 deletions.
87 changes: 45 additions & 42 deletions src/redux/HOC.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,70 +9,73 @@ export const hocCreateTypes = (baseType) => ({

const checkHocTypes = types => {
const { START, SUCCESS, ERROR } = types;

if (!START || !SUCCESS || !ERROR) {
return false;
}

if (START === '') {
if (START.indexOf('START') < 0) {
return false;
}

if (SUCCESS === '') {
if (SUCCESS.indexOf('SUCCESS') < 0) {
return false;
}

if (ERROR === '') {
if (ERROR.indexOf('ERROR') < 0) {
return false;
}

return true;
};

// actions
export const hocAsyncAction = (ACTION_TYPE, createThunk, userOptions) => (...args) => {
const { rejectable, handled } = {
rejectable: false,
handled: true,
...userOptions,
};

// create function for passed properties / args
const thunk = createThunk(...args);

export const hocAsyncAction = (ACTION_TYPE, createThunk, userOptions) => {
// basic check
if (!checkHocTypes(ACTION_TYPE)) {
console.log(`No valid types specified for ${JSON.stringify(ACTION_TYPE)}`);
return Promise.reject(new Error('No valid ACTION_TYPE specified'));
throw new Error('No valid ACTION_TYPE specified');
}

return (dispatch) => {
// start async job
dispatch({ type: ACTION_TYPE.START });

// except to receive promise from create thunk method
return dispatch(thunk)
// proceed with success
.then(payload => {
dispatch({
type: ACTION_TYPE.SUCCESS,
payload,
});

return Promise.resolve(payload);
})
// proceed with error
.catch((error) => {
dispatch({
type: ACTION_TYPE.ERROR,
payload: createNetworkError(error),
error: handled, // only flag as error if not rejectable
// actual HOC action
return (...args) => {
const { rejectable, handled } = {
rejectable: false,
handled: true,
...userOptions,
};

// create function for passed properties / args
const thunk = createThunk(...args);

return (dispatch) => {
// start async job
dispatch({ type: ACTION_TYPE.START });

// except to receive promise from create thunk method
return dispatch(thunk)
// proceed with success
.then(payload => {
dispatch({
type: ACTION_TYPE.SUCCESS,
payload,
});

return Promise.resolve(payload);
})
// proceed with error
.catch((error) => {
dispatch({
type: ACTION_TYPE.ERROR,
payload: createNetworkError(error),
error: handled, // only flag as error if not rejectable
});

// only reject error if asked by user
if (rejectable) return Promise.reject(error);
});

// only reject error if asked by user
if (rejectable) return Promise.reject(error);
});
}
}
};
};

// export reducer
Expand All @@ -84,7 +87,7 @@ export default ({
// basic check
if (!checkHocTypes(ACTION_TYPE)) {
console.log(`No valid types specified for ${JSON.stringify(ACTION_TYPE)}`);
return Promise.reject(new Error('No valid ACTION_TYPE specified'));
throw new Error('No valid ACTION_TYPE specified');
}

const defaultInitialState = {
Expand Down
244 changes: 239 additions & 5 deletions src/redux/__tests__/HOC.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* global testUtils */
import hocReducer, { hocCreateTypes, __testables__ } from '../HOC';
import hocReducer, { hocCreateTypes, __testables__, hocAsyncAction } from '../HOC';
import * as reduxApplication from '../application';
import { createNetworkError } from '../../util/ErrorHandler';

describe('HOCH test suite', () => {
const baseType = 'HOC_TEST';
Expand All @@ -25,15 +26,83 @@ describe('HOCH test suite', () => {
.toEqual(ACTION_TYPE);
});

it('check checkHocTypes', () => {
expect(__testables__.checkHocTypes({
describe('HOC checkHocTypes test suite', () => {
const hocType = {
START: 'START',
SUCCESS: 'SUCCESS',
ERROR: 'ERROR'
})).toEqual(true);
};

it('check checkHocTypes default', () => {
expect(__testables__.checkHocTypes(hocType)).toEqual(true);
});

it('check for unset / invalid START', () => {
const { START, ...other } = hocType
expect(__testables__.checkHocTypes({ ...other }))
.toBeFalsy();

expect(__testables__.checkHocTypes({
...hocType,
START: 'invalid',
}))
.toBeFalsy();
});

it('check for unset / invalid SUCCESS', () => {
const { SUCCESS, ...other } = hocType
expect(__testables__.checkHocTypes({ ...other }))
.toBeFalsy();

expect(__testables__.checkHocTypes({
...hocType,
SUCCESS: 'invalid',
}))
.toBeFalsy();
});

it('check for unset / invalid ERROR', () => {
const { ERROR, ...other } = hocType
expect(__testables__.checkHocTypes({ ...other }))
.toBeFalsy();

expect(__testables__.checkHocTypes({
...hocType,
ERROR: 'invalid',
}))
.toBeFalsy();
});
});

describe('HOC hocAsyncAction test suite', () => {
describe('HOC reducer test suite', () => {
let consoleLogMock;

beforeEach(() => {
consoleLogMock = jest.spyOn(console, 'log')
.mockImplementation(() => ({}));
});

afterEach(() => {
consoleLogMock.mockRestore();
});

it('should reject on invalid HOC action types', async (done) => {
const invalidActionTypes = {};

try {
hocReducer({
ACTION_TYPE: invalidActionTypes,
noData: false,
});
expect(true).toBeFalsy();
} catch (error) {
expect(consoleLogMock).toBeCalledTimes(1);
expect(error).toBeInstanceOf(Error);
expect(error.message).toEqual('No valid ACTION_TYPE specified');
done();
}
});

it('should generate initial reducer state', () => {
const reducer = hocReducer({
ACTION_TYPE: ACTION_TYPE,
Expand Down Expand Up @@ -109,6 +178,171 @@ describe('HOCH test suite', () => {
});

describe('HOC hocAsyncAction test suite', () => {
const API = {};
const mockstore = testUtils.createMockStoreWithApi(API);

let consoleLogMock;

beforeEach(() => {
consoleLogMock = jest.spyOn(console, 'log')
.mockImplementation(() => ({}));
});

afterEach(() => {
consoleLogMock.mockRestore();
});

it('should reject on invalid HOC action types', async (done) => {
const invalidActionTypes = {};

try {
hocAsyncAction(
invalidActionTypes,
() => () => {
return Promise.resolve();
}
);
expect(true).toBeFalsy();
} catch(error) {
expect(consoleLogMock).toBeCalledTimes(1);
expect(error).toBeInstanceOf(Error);
expect(error.message).toEqual('No valid ACTION_TYPE specified');
done();
}
});

it('should handle ACTION_TYPE.SUCCESS', async (done) => {
const store = mockstore();

const payload = {
message: 'hello',
};

const HOC_ACTIONS = testUtils.createHocActions({
baseType: baseType,
payload,
});

const expectedActions = [
HOC_ACTIONS.START,
HOC_ACTIONS.SUCCESS,
];

const actionAttr = {
value: null,
};

const hocAction = hocAsyncAction(
ACTION_TYPE,
(input) => (dispatch, getState, createThunk) => {
// check input vars
expect(input).toEqual(actionAttr);

expect(dispatch).toBeInstanceOf(Function);
expect(getState).toBeInstanceOf(Function);
expect(createThunk).toBeInstanceOf(Object);

return Promise.resolve(payload);
}
);

await hocAction(actionAttr)(store.dispatch);

expect(store.getActions()).toEqual(expectedActions);
done();
});

it('should handle ACTION_TYPE.ERROR', async (done) => {
const store = mockstore();

const error = new Error('custom error');
const payload = createNetworkError(error);

const HOC_ACTIONS = testUtils.createHocActions({
baseType: baseType,
payload,
error,
errorIsHandled: true,
});

const expectedActions = [
HOC_ACTIONS.START,
HOC_ACTIONS.ERROR,
];

const actionAttr = {
value: null,
};

const hocAction = hocAsyncAction(
ACTION_TYPE,
(input) => (dispatch, getState, createThunk) => {
// check input vars
expect(input).toEqual(actionAttr);

expect(dispatch).toBeInstanceOf(Function);
expect(getState).toBeInstanceOf(Function);
expect(createThunk).toBeInstanceOf(Object);

return Promise.reject(error);
}
);

await hocAction(actionAttr)(store.dispatch);

expect(store.getActions()).toEqual(expectedActions);
done();
});

it('should handle ACTION_TYPE.ERROR with rejectable', async (done) => {
const store = mockstore();

const error = new Error('custom error');
const payload = createNetworkError(error);

const HOC_ACTIONS = testUtils.createHocActions({
baseType: baseType,
payload,
error,
errorIsHandled: true,
});

const expectedActions = [
HOC_ACTIONS.START,
HOC_ACTIONS.ERROR,
];

const actionAttr = {
value: null,
};

const hocAction = hocAsyncAction(
ACTION_TYPE,
(input) => (dispatch, getState, createThunk) => {
// check input vars
expect(input).toEqual(actionAttr);

expect(dispatch).toBeInstanceOf(Function);
expect(getState).toBeInstanceOf(Function);
expect(createThunk).toBeInstanceOf(Object);

return Promise.reject(error);
},
{
rejectable: true,
}
);

try {
await hocAction(actionAttr)(store.dispatch);

// FAIL here as not happy test path
expect(true).toBeFalsy();
} catch (actionError) {
expect(store.getActions()).toEqual(expectedActions);
expect(actionError).toEqual(error);
done();
}
});
});
});

0 comments on commit 221b402

Please sign in to comment.