Skip to content

Instantly share code, notes, and snippets.

@sriramrudraraju
Last active November 2, 2017 16:49
Show Gist options
  • Save sriramrudraraju/3596312c539d3958da51203a0f088b86 to your computer and use it in GitHub Desktop.
Save sriramrudraraju/3596312c539d3958da51203a0f088b86 to your computer and use it in GitHub Desktop.
React Redux Unit Testing with Jest part 1: Action Creators
// @flow
// Actions for setting DOM properties
import { SET_H1, SET_TITLE } from './types';
type DomActionType = {
type: string,
payload: string
};
//Action for setting H1 element
export const setH1 = (text: string): DomActionType => {
return { type: SET_H1, payload: text };
};
//Action for setting title
export const setTitle = (text: string): DomActionType => {
return { type: SET_TITLE, payload: text };
};
import { setH1, setTitle } from './dom_actions';
//test suite for dom actions
describe('dom_actions', () => {
//test suite for setH1 action creator
describe('setH1', () => {
//checking for its type
it('should have type of "SET_H1"', () => {
expect(setH1().type).toBe('SET_H1');
});
//checking for parameter passing as payload
it('should pass on to payload as we pass in params', () => {
let text = 'some random string';
expect(setH1(text).payload).toBe(text);
});
});
//test suite for setTitle action creator
describe('setTitle', () => {
//checking fro its type
it('should have type "SET_TITLE"', () => {
expect(setTitle().type).toBe('SET_TITLE');
});
//checking for parameter passing as payload
it('should pass on to payload as we pass in params', () => {
let text = 'some random string';
expect(setTitle(text).payload).toBe(text);
});
});
});
// @flow
import type {
ActionAsyncType,
ActionType,
DispatchType
} from '../flow-types/actions';
import 'whatwg-fetch';
export default (
url: string,
body: Object | null = null,
request: (() => ActionType) | null = null,
receive: ((Payload: any) => ActionType) | null = null,
error: ((Payload: string) => ActionType) | null = null,
pre: ((State: Object) => boolean) | null = null
): ActionAsyncType => {
// TODO: Does this return Promise<ActionType>?
return (
dispatch: DispatchType,
getState: () => Object
): Promise<ActionType | void> => {
// If we have a pre-condition to fetching, check it.
// kind of cache,
if (pre) {
if (!pre(getState())) {
return Promise.resolve();
}
}
// Action: Requesting data.
if (request) {
dispatch(request());
}
// Fetch
let f = fetch(
url,
Object.assign({}, body)
).then((response: Object): Object => response.json());
// Action: Receiving data.
if (receive) {
// Fix: Flow erroneously believes `receive` to potentially be null at this point.
const r = receive;
f = f.then((data: Object): ActionType => dispatch(r(data)));
}
// Action: Error receiving data.
if (error) {
// Fix: Flow erroneously believes `error` to potentially be null at this point.
const r = error;
f = f.catch((e: string): ActionType => dispatch(r(e.toString())));
}
return f;
};
};
// @flow
import type {
ActionAsyncType,
ActionNoPayloadType,
ActionPayloadObjectType,
ActionPayloadStringType
} from "../../flow-types/actions";
import type { StateSurveysType } from "../../flow-types/redux";
//we created seperate function to make the fetch fetch calls depending on the requests
import asyncActionCreator from "../async-action-creator";
export const requestSurveyList = (): ActionNoPayloadType => {
return { type: "REQUEST_SURVEY_LIST" };
};
export const receiveSurveyList = (payload: Object): ActionPayloadObjectType => {
return { type: "RECEIVE_SURVEY_LIST", payload: payload };
};
export const surveyListError = (payload: string): ActionPayloadStringType => {
return { type: "SURVEY_LIST_ERROR", payload: payload };
};
export const fetchSurveyList = (): ActionAsyncType => {
return asyncActionCreator(
"http://blahblagblah",
{
method: "POST",
headers: {
"Content-Type": "application/json; charset=utf-8"
}
},
requestSurveyList,
receiveSurveyList,
surveyListError,
(state: StateSurveysType): boolean => {
return !state.surveys.surveyList.length;
}
);
};
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import { fetchSurveyList } from "../surveys/list";
//test suite for surveys/list actions
describe("survey-list", () => {
let middlewares, mockStore, mockResponse, store;
beforeEach(() => {
//since we are using thunk as middleware, so load it
middlewares = [thunk];
//mockStore mocks the store behaviour
mockStore = configureMockStore(middlewares);
//instead calling api we will be calling mocks to get the response
//generic mock response function will be as below
mockResponse = (status, statusText, response) => {
return new window.Response(response, {
status: status,
statusText: statusText,
headers: {
"Content-type": "application/json"
}
});
};
});
//when the action creator call the api and gets a success response with no cache
it("calls request with no cache and fetch response was successful", () => {
//creating fake fetch with success response
global.fetch = jest
.fn()
.mockImplementation(() =>
Promise.resolve(
mockResponse(200, null, '{"ids":{"provider":' + "123" + "}}")
)
);
//setting the cache condition to false by making empty
store = mockStore({
surveys: {
surveyList: []
}
});
//trigger the action
return store.dispatch(fetchSurveyList()).then(() => {
//all the consequent actions will be available at .getActions()
const expectedActions = store.getActions();
//sice it has request action and request sucess, so length will be 2
expect(expectedActions.length).toBe(2);
// last action should be success action, since we not calling other actions
expect(expectedActions[length + 1].type).toBe("RECEIVE_SURVEY_LIST");
});
});
//call the action and response is failure with nocache
it("calls request with no cache and fetch response was error", () => {
//creating fake fetch with error response
global.fetch = jest
.fn()
.mockImplementation(() =>
Promise.reject(
mockResponse(500, null, '{"ids":{"provider":' + "123" + "}}")
)
);
//setting the cache condition to false by making empty
store = mockStore({
surveys: {
surveyList: []
}
});
//trigger the action
return store.dispatch(fetchSurveyList()).then(() => {
//all the consequent actions will be available at .getActions()
const expectedActions = store.getActions();
//sice it has request action and request error, so length will be 2
expect(expectedActions.length).toBe(2);
// last action should be error action, since we not calling other actions
expect(expectedActions[length + 1].type).toBe("SURVEY_LIST_ERROR");
});
});
//when trying to call the api with a cache
it("calls request with cache and fetch response was success", () => {
//creating fake fetch with success response
global.fetch = jest
.fn()
.mockImplementation(() =>
Promise.resolve(
mockResponse(500, null, '{"ids":{"provider":' + "123" + "}}")
)
);
// setting some random cache for making cache condition to be true
store = mockStore({
surveys: {
surveyList: ["some", "random", "values"]
}
});
//trigger the action
return store.dispatch(fetchSurveyList()).then(() => {
//all the consequent actions will be available at .getActions()
const expectedActions = store.getActions();
//because of cache, no actions will be called, it will be 0
expect(expectedActions.length).toBe(0);
});
});
});

React Redux Unit Testing: Part 1

Example for Action Creators using jest

Thanks to Max Stoiber for his blog on regular actions

Thanks to Ferran Negre for his blog on async actions

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