Skip to content

Instantly share code, notes, and snippets.

@sharvit
Last active January 15, 2018 09:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sharvit/cf658a8494758be8062a63121b925fdc to your computer and use it in GitHub Desktop.
Save sharvit/cf658a8494758be8062a63121b925fdc to your computer and use it in GitHub Desktop.
Testing redux actions

Testing redux actions

Goals:

  1. Create a pure unit-testing

    • Do net allow the tests to get out from the file context
    • Mock all imports and treat them as simple inputs
    • Do not use store to test the actions
  2. Use snapshots

  3. Use cynical data input for the tests

  4. Get 100% file coverage

import URI from 'urijs';
import $ from 'jquery';
import {
BOOKMARKS_REQUEST,
BOOKMARKS_SUCCESS,
BOOKMARKS_FAILURE,
BOOKMARKS_MODAL_OPENED,
BOOKMARKS_MODAL_CLOSED,
} from '../../consts';
import { ajaxRequestAction } from '../common';
const _getBookmarks = (url, controller) => dispatch =>
ajaxRequestAction({
dispatch,
requestAction: BOOKMARKS_REQUEST,
successAction: BOOKMARKS_SUCCESS,
failedAction: BOOKMARKS_FAILURE,
url,
item: { controller },
});
export const getBookmarks = (url, controller) => {
const uri = new URI(url);
// eslint-disable-next-line camelcase
uri.setSearch({ search: `controller=${controller}`, per_page: 100 });
return _getBookmarks(uri.toString(), controller);
};
export const modalOpened = () => ({
type: BOOKMARKS_MODAL_OPENED,
payload: {
query: ($('#search').val() || '').trim(),
},
});
export const modalClosed = () => ({
type: BOOKMARKS_MODAL_CLOSED,
});
import { ajaxRequestAction } from '../common';
import { getBookmarks, modalOpened, modalClosed } from './bookmarksActions';
jest.mock('../common');
describe('bookmark actions', () => {
// reset mocks before each test
beforeEach(() => jest.resetAllMocks());
it('should get bookmarks', () => {
const dispatcher = getBookmarks('some-url', 'some-controller');
dispatcher(jest.fn());
expect(ajaxRequestAction).toMatchSnapshot();
});
it('should create BOOKMARKS_MODAL_OPENED action', () => {
const results = modalOpened();
expect(results).toMatchSnapshot();
});
it('should create BOOKMARKS_MODAL_CLOSED action', () => {
const results = modalClosed();
expect(results).toMatchSnapshot();
});
});
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`bookmark actions should create BOOKMARKS_MODAL_CLOSED action 1`] = `
Object {
"type": "BOOKMARKS_MODAL_CLOSED",
}
`;
exports[`bookmark actions should create BOOKMARKS_MODAL_OPENED action 1`] = `
Object {
"payload": Object {
"query": "",
},
"type": "BOOKMARKS_MODAL_OPENED",
}
`;
exports[`bookmark actions should get bookmarks 1`] = `
[MockFunction] {
"calls": Array [
Array [
Object {
"dispatch": [MockFunction],
"failedAction": "BOOKMARKS_FAILURE",
"item": Object {
"controller": "some-controller",
},
"requestAction": "BOOKMARKS_REQUEST",
"successAction": "BOOKMARKS_SUCCESS",
"url": "some-url?search=controller%3Dsome-controller&per_page=100",
},
],
],
}
`;
// eslint-disable-next-line import/no-extraneous-dependencies
import { SubmissionError } from 'redux-form';
import API from '../../../API';
import { addToast } from '../toasts';
const fieldErrors = ({ error }) => {
const { errors } = error;
if (errors.base) {
errors._error = errors.base;
delete errors.base;
}
return new SubmissionError(errors);
};
const onError = (error) => {
if (error.response.status === 422) {
// Handle invalid form data
throw fieldErrors(error.response.data);
}
throw new SubmissionError({
_error: [`${__('Error submitting data:')} ${error.response.status} ${__(error.response.statusText)}`],
});
};
const verifyProps = (item, values) => {
if (!item) {
throw new Error('item must be defined, e.g. Bookmark');
}
if (!values) {
throw new Error('values must be defined');
}
};
export const submitForm = ({
item, url, values, method = 'post',
}) => {
verifyProps(item, values);
return dispatch =>
API[method](url, values)
.then(({ data }) => {
dispatch({
type: `${item.toUpperCase()}_FORM_SUBMITTED`,
payload: { item, data },
});
dispatch(addToast({
type: 'success',
// eslint-disable-next-line no-undef
message: Jed.sprintf('%s was successfully created.', __(item)),
}));
})
.catch(onError);
};
import { SubmissionError } from 'redux-form';
import API from '../../../API';
import { addToast } from '../toasts';
import { submitForm } from './formsActions';
jest.mock('redux-form');
jest.mock('../../../API');
jest.mock('../toasts');
describe('form actions', () => {
// reset mocks before each test
beforeEach(() => jest.resetAllMocks());
test('SubmitForm must include an object item/values', () => {
expect(() => submitForm()).toThrow(Error);
expect(() => submitForm({ url: 'http://example.com' })).toThrow(Error);
expect(() => submitForm({ item: 'Resource' })).toThrow(Error);
expect(() => submitForm({ values: { a: 1 } })).toThrow(Error);
});
test('should submit form correctly', async () => {
API.post.mockImplementation(async () => ({ data: 'some-data' }));
addToast.mockImplementation(() => 'some-toast');
const dispatch = jest.fn();
const dispatcher = submitForm({
values: 'some-values',
url: 'some-url',
item: 'some-item',
});
await dispatcher(dispatch);
expect(API.post).toMatchSnapshot();
expect(addToast).toMatchSnapshot();
expect(dispatch).toMatchSnapshot();
});
test('should fail correctly when submiting form', async () => {
const dispatch = jest.fn();
API.post.mockImplementation(async () => {
// eslint-disable-next-line no-throw-literal
throw {
response: {
status: 'some-status',
statusText: 'some-status-text',
data: 'some-data',
},
};
});
const dispatcher = submitForm({
values: 'some-values',
url: 'some-url',
item: 'some-item',
});
await expect(dispatcher(dispatch)).rejects.toThrow(SubmissionError);
expect(SubmissionError).toMatchSnapshot();
expect(API.post).toMatchSnapshot();
expect(addToast).toMatchSnapshot();
expect(dispatch).toMatchSnapshot();
});
test('should fail correctly with status=422 when submiting form', async () => {
const dispatch = jest.fn();
API.post.mockImplementation(async () => {
// eslint-disable-next-line no-throw-literal
throw {
response: {
status: 422,
statusText: 'some-status-text',
data: { error: { errors: { base: 'some-error', name: 'some-name' } } },
},
};
});
const dispatcher = submitForm({
values: 'some-values',
url: 'some-url',
item: 'some-item',
});
await expect(dispatcher(dispatch)).rejects.toThrow(SubmissionError);
expect(SubmissionError).toMatchSnapshot();
expect(API.post).toMatchSnapshot();
expect(addToast).toMatchSnapshot();
expect(dispatch).toMatchSnapshot();
});
});
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`form actions should fail correctly when submiting form 1`] = `
[MockFunction] {
"calls": Array [
Array [
Object {
"_error": Array [
"Error submitting data: some-status some-status-text",
],
},
],
],
}
`;
exports[`form actions should fail correctly when submiting form 2`] = `
[MockFunction] {
"calls": Array [
Array [
"some-url",
"some-values",
],
],
}
`;
exports[`form actions should fail correctly when submiting form 3`] = `[MockFunction]`;
exports[`form actions should fail correctly when submiting form 4`] = `[MockFunction]`;
exports[`form actions should fail correctly with status=422 when submiting form 1`] = `
[MockFunction] {
"calls": Array [
Array [
Object {
"_error": "some-error",
"name": "some-name",
},
],
],
}
`;
exports[`form actions should fail correctly with status=422 when submiting form 2`] = `
[MockFunction] {
"calls": Array [
Array [
"some-url",
"some-values",
],
],
}
`;
exports[`form actions should fail correctly with status=422 when submiting form 3`] = `[MockFunction]`;
exports[`form actions should fail correctly with status=422 when submiting form 4`] = `[MockFunction]`;
exports[`form actions should submit form correctly 1`] = `
[MockFunction] {
"calls": Array [
Array [
"some-url",
"some-values",
],
],
}
`;
exports[`form actions should submit form correctly 2`] = `
[MockFunction] {
"calls": Array [
Array [
Object {
"message": "%s was successfully created.",
"type": "success",
},
],
],
}
`;
exports[`form actions should submit form correctly 3`] = `
[MockFunction] {
"calls": Array [
Array [
Object {
"payload": Object {
"data": "some-data",
"item": "some-item",
},
"type": "SOME-ITEM_FORM_SUBMITTED",
},
],
Array [
"some-toast",
],
],
}
`;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment