How to write JEST test cases for the callback API - reactjs

I have created a API service factory, passing dynamically URL, function as a parameter. Once it success data comes in the callback function and its working fine as per expected. For same I am going to write JEST test cases. I couldn't fine the right approach to do it. Can help someone. Great appreciate.
Code is here
function userLogin(username, password) {
const reqBody = {
companyEmailAddress: username,
password,
};
const url = `${config.apiBaseUrl}${serviceMethodConstants.login}`;
return (dispatch) => {
dispatch(serviceFactory.postData(url, false, reqBody, function (response, dispatch) {
if (response !== undefined) {
console.log(response )
}
}));
};
}
For same I wrote JEST test case, but it is not showing any error or success message as expected.
JEST test code
import { userConstants } from './constants';
import { serviceFactory } from '../../services/_helpers/serviceFactory';
const loginData = {
companyEmailAddress: 'rameshffdfdf.lambanihghgh#gmail.com',
password: 'Ramesh#1',
};
axiosMock.onPost(routeUrl).reply(200, JSON.stringify(loginData));
const spy = jest.spyOn(axios, 'post');
await store.dispatch(userActions.userLogin(...loginData, function (response, dispatch) {
expect(response.message).toEqual('Failure');
expect(spy).toBeCalled();
}));

userLogin action creator (a thunk) doesn't accept a callback and doesn't do a request. It's unknown whether store.dispatch returns a promise that could be awaited.
A proper strategy for unit testing is to mock everything but tested unit. Since serviceFactory abstraction is in use, Axios shouldn't be involved. Action creator can be tested without Redux involved, either.
const dispatch = jest.fn();
const postDataResult = {};
jest.spyOn(serviceFactory, 'postData').mockReturnValue(postDataResult);
userActions.userLogin('user', 'pass')(dispatch);
expect(serviceFactory.postData).toBeCalledWith(url, false, {...}, expect.any(Function));
expect(dispatch).toBeCalledWith(postDataResult);
The test can stay synchronous this way.

Related

Jest to test a class method which has inner function

I'm writing unit test for once of my .ts file. Where I'm facing a problem and unable to find the solution. Hopefully someone can help me to resolve it.
Problem
While writing unit test. I'm unable to test the value for profile. After calling a method called getProfile().
File setup
Profile.ts
import { getProfileAPI} from "./api";
class ProfileDetails implements IProfileDetails {
public profile: string = ''
constructor() {}
getProfile = async () => {
const { data } = await getProfileAPI();
if (data) {
this.profile = data
}
};
}
const profileDetail = new ProfileDetails();
export default profileDetail;
Profile.spec.ts
import Profile from './Profile';
describe('Profile', () => {
it('getProfile', async () => {
Profile.getProfile = jest.fn();
await Profile.getProfile();
expect(Profile.getProfile).toHaveBeenCalled();
});
});
So the challenge I'm facing here is, I can able to mock the getProfile method. But I'm not able to mock the getProfileAPI function which is called inside the getProfile method.
How can I mock a function which is called inside a mocked method (or) is there any other way to resolve this. Kindly help.
Thanks in advance.
Before answering your questions, I may have some comments :
your test is wrong, all it does is calling the method then checking if it is called, of course it will always pass !
you are not really mocking, in fact you're erasing the old method and it may have some impacts on other tests.
your method "getProfile" should be called "getAndSetProfile", or "syncProfile", or something like that, getProfile is confusing for a developer, he will think it only get the profile and returns it.
I don't recommend creating & exporting an instance of ProfileDetails like this, you should take a look on DI (Dependency Injection) with typedi for example.
Do not forget :
A unit test means that any dependency inside your "unit" should be mock, you must only test the logic inside your "unit" (in your case, the getProfile function, or the class itself).
Here, you are invoking a method called "getProfileAPI" from another service that is not mocked, so you are currently testing its logic too.
This test should work :
Profile.spec.ts
jest.mock('./api', () => ({
getProfileAPI: jest.fn(),
}));
import { getProfileAPI } from "./api";
import Profile from './Profile';
describe('Profile', () => {
it('getProfile', async () => {
await Profile.getProfile();
expect(getProfileAPI).toHaveBeenCalled();
});
});
In our example, Profile.profile will be empty, because even if we mocked to getProfileAPI method, we didn't make it return something. You could test both cases :
jest.mock('./api', () => ({
getProfileAPI: jest.fn(),
}));
import { getProfileAPI } from "./api";
import Profile from './Profile';
const mockGetProfileAPI = getProfileAPI as jest.Mock; // Typescript fix for mocks, else mockResolvedValue method will show an error
describe('Profile', () => {
describe('getProfile', () => {
describe('with data', () => {
const profile = 'TEST_PROFILE';
beforeEach(() => {
mockGetProfileAPI.mockResolvedValue({
data: profile,
});
});
it('should call getProfileAPI method', async () => {
await Profile.getProfile();
expect(mockGetProfileAPI).toHaveBeenCalled(); // Please note that "expect(getProfileAPI).toHaveBeenCalled();" would work
});
it('should set profile', async () => {
await Profile.getProfile();
expect(Profile.profile).toBe(profile);
});
});
describe.skip('with no data', () => {
it('should not set profile', async () => {
await Profile.getProfile();
expect(Profile.profile).toStrictEqual(''); // the default value
});
});
});
});
NB : I skipped the last test because it won't work in your case. Profile isn't recreated between tests, and as it is an object, it keeps the value of Profile.profile (btw, this is a bit weird) between each tests. This is one of the reasons why you should not export a new instance of the class.

Mocking an asyc function in react using Jest

I am new to react and jest.
I am getting stuck on the right way to mock an async function even after scouring many articles on this.
here is my scenario. I am pasting the code which is giving me trouble. I have the following function defined. I want to mock the getToken() function. The returned token is a string.
export async getSignin() {
const token = await getToken()
//do something with this token
}
export async function getToken(){
const token = (await accessToken())
return token
}
Test code:
it(" returns a valid user ", async () => {
const getToken = jest
.fn()
.mockImplementation(async () => Promise.resolve("abcd"))
const signedin = await getSignin()
}
when I do this, my expectation is that the code will use the mock implementation of the getToken and proceed. What I am getting is that it is throwing an error at accessToken(). My understanding of mock is that it should not go into the actual implementation and call accessToken()
what am I doing wrong here?
fetch-mock is a great package for mocking API requests in your test files! http://www.wheresrhys.co.uk/fetch-mock
After installing, in your beforeEach block in a test file you can now mock the payload from API calls. It will look something like this:
beforeEach(() => {
fetchMock.mock('/api/users'/1, { id: 1, name: 'Test User Name', address: 'Test User Address'})
})

Testing typescript asyc functions, Async callback was not invoked even though it's called in .then() method

I've been searching around for a while for a resolution to this but can't find anything, the only thing I can think of is redux-mock-store doesn't support Promises.
I have this test method below.
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
import { cleanAll } from 'nock';
var middleware = [thunk];
var mockStore = configureMockStore(middleware);
describe('Organisation thunk actions', () => {
afterAll(() => {
cleanAll();
});
describe('getOrganisation', () => {
test('should create BEGIN_GET_ORGANISATION_AJAX action when getting organsation', (done: any) => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
var expectedAction = [{ type: types.BEGIN_GET_ORGANISATION_AJAX }];
var store = mockStore({});
store.dispatch<any>((OrganisationActions.getOrganisation("e"))).then(() => {
var actions = store.getActions();
expect(actions).toEqual(expectedAction);
done();
});
});
});
});
This is designed to test the action below.
export const getOrganisation = (organisationId: string, expands?: string[]) => {
return (dispatch: any) => {
dispatch(beginGetOrganisationAjax());
return OrganisationApi.getOrganisationAsync(organisationId).then((organisation: any) => {
dispatch(getOrganisationSuccess(organisation))
}).catch(error => {
throw (error);
});
}
}
Where OrganisationApi.getOrganisationAsync(organisationId) is a call to a mockApi that I know works from usage.
When I run this test it fails twice after the 30s DEFAULT_TIMEOUT_INTERVAL specified, once as expected (as I've designed the expect to) but the second failure is an error "Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.".
Unless I'm mistaken, the async callback is the done() function invoked after the expect(actions).toEqual(expectedAction) within the .then() function in the test.
As the expect fails the .then() is definitely running but doesn't seem to be running the .done() function, can anyone see why this might be happening?
Finally figured out the issue, posting the resolution in case someone has a similar problem.
The Async callback is never called as the test function quits out as soon as it comes across the failing expect() statement, therefore it never actually runs the done() function in this instance.
Once this test passes it calls the done() function and my tests pass.

Testing dispatched actions in Redux thunk with Jest

I'm quite new to Jest and admittedly am no expert at testing async code...
I have a simple Fetch helper I use:
export function fetchHelper(url, opts) {
return fetch(url, options)
.then((response) => {
if (response.ok) {
return Promise.resolve(response);
}
const error = new Error(response.statusText || response.status);
error.response = response;
return Promise.reject(error);
});
}
And implement it like so:
export function getSomeData() {
return (dispatch) => {
return fetchHelper('http://datasource.com/').then((res) => {
dispatch(setLoading(true));
return res.json();
}).then((data) => {
dispatch(setData(data));
dispatch(setLoading(false));
}).catch(() => {
dispatch(setFail());
dispatch(setLoading(false));
});
};
}
However I want to test that the correct dispatches are fired in the correct circumstances and in the correct order.
This used to be quite easy with a sinon.spy(), but I can't quite figure out how to replicate this in Jest. Ideally I'd like my test to look something like this:
expect(spy.args[0][0]).toBe({
type: SET_LOADING_STATE,
value: true,
});
expect(spy.args[1][0]).toBe({
type: SET_DATA,
value: {...},
});
Thanks in advance for any help or advice!
Answer as of January 2018
The redux docs have a great article on testing async action creators*:
For async action creators using Redux Thunk or other middleware, it's best to completely mock the Redux store for tests. You can apply the middleware to a mock store using redux-mock-store. You can also use fetch-mock to mock the HTTP requests.
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as actions from '../../actions/TodoActions'
import * as types from '../../constants/ActionTypes'
import fetchMock from 'fetch-mock'
import expect from 'expect' // You can use any testing library
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
describe('async actions', () => {
afterEach(() => {
fetchMock.reset()
fetchMock.restore()
})
it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', () => {
fetchMock
.getOnce('/todos', { body: { todos: ['do something'] }, headers: { 'content-type': 'application/json' } })
const expectedActions = [
{ type: types.FETCH_TODOS_REQUEST },
{ type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } }
]
const store = mockStore({ todos: [] })
return store.dispatch(actions.fetchTodos()).then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
})
})
Their approach is not to use jest (or sinon) to spy, but to use a mock store and assert the dispatched actions. This has the advantage of being able to handle thunks dispatching thunks, which can be very difficult to do with spies.
This is all straight from the docs, but let me know if you want me to create an example for your thunk.
* (this quote is no longer in the article as of January 2023 and the recommendations have changed dramatically, see comments on this answer for further info)
Answer as of January 2018
For async action creators using Redux Thunk or other middleware, it's best to completely mock the Redux store for tests. You can apply the middleware to a mock store using redux-mock-store. In order to mock the HTTP request, you can make use of nock.
According to redux-mock-store documentation, you will need to call store.getActions() at the end of the request to test asynchronous actions, you can configure your test like
mockStore(getState?: Object,Function) => store: Function Returns an
instance of the configured mock store. If you want to reset your store
after every test, you should call this function.
store.dispatch(action) => action Dispatches an action through the
mock store. The action will be stored in an array inside the instance
and executed.
store.getState() => state: Object Returns the state of the mock
store
store.getActions() => actions: Array Returns the actions of the mock
store
store.clearActions() Clears the stored actions
You can write the test action like
import nock from 'nock';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
//Configuring a mockStore
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
//Import your actions here
import {setLoading, setData, setFail} from '/path/to/actions';
test('test getSomeData', () => {
const store = mockStore({});
nock('http://datasource.com/', {
reqheaders // you can optionally pass the headers here
}).reply(200, yourMockResponseHere);
const expectedActions = [
setLoading(true),
setData(yourMockResponseHere),
setLoading(false)
];
const dispatchedStore = store.dispatch(
getSomeData()
);
return dispatchedStore.then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
P.S. Keep in ming that the mock-store does't update itself when the mocked action are fired and if you are depending on the updated data after the previous action to be used in the next action then you need to write your own instance of it like
const getMockStore = (actions) => {
//action returns the sequence of actions fired and
// hence you can return the store values based the action
if(typeof action[0] === 'undefined') {
return {
reducer: {isLoading: true}
}
} else {
// loop over the actions here and implement what you need just like reducer
}
}
and then configure the mockStore like
const store = mockStore(getMockStore);
Hope it helps. Also check this in redux documentation on testing async action creators
If you're mocking the dispatch function with jest.fn(), you can just access dispatch.mock.calls to get all the calls made to your stub.
const dispatch = jest.fn();
actions.yourAction()(dispatch);
expect(dispatch.mock.calls.length).toBe(1);
expect(dispatch.mock.calls[0]).toBe({
type: SET_DATA,
value: {...},
});
In my answer I am using axios instead of fetch as I don't have much experience on fetch promises, that should not matter to your question. I personally feel very comfortable with axios.
Look at the code sample that I am providing below:
// apiCalls.js
const fetchHelper = (url) => {
return axios.get(url);
}
import * as apiCalls from './apiCalls'
describe('getSomeData', () => {
it('should dispatch SET_LOADING_STATE on start of call', async () => {
spyOn(apiCalls, 'fetchHelper').and.returnValue(Promise.resolve());
const mockDispatch = jest.fn();
await getSomeData()(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: SET_LOADING_STATE,
value: true,
});
});
it('should dispatch SET_DATA action on successful api call', async () => {
spyOn(apiCalls, 'fetchHelper').and.returnValue(Promise.resolve());
const mockDispatch = jest.fn();
await getSomeData()(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: SET_DATA,
value: { ...},
});
});
it('should dispatch SET_FAIL action on failed api call', async () => {
spyOn(apiCalls, 'fetchHelper').and.returnValue(Promise.reject());
const mockDispatch = jest.fn();
await getSomeData()(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: SET_FAIL,
});
});
});
Here I am mocking the fetch helper to return Resolved promise to test success part and reject promise to test failed api call. You can pass arguments to them to validate on response also.
You can implement getSomeData like this:
const getSomeData = () => {
return (dispatch) => {
dispatch(setLoading(true));
return fetchHelper('http://datasource.com/')
.then(response => {
dispatch(setData(response.data));
dispatch(setLoading(false));
})
.catch(error => {
dispatch(setFail());
dispatch(setLoading(false));
})
}
}
I hope this solves your problem. Please comment, if you need any clarification.
P.S You can see by looking at above code why I prefer axios over fetch, saves you from lot of promise resolves! For further reading on it you can refer: https://medium.com/#thejasonfile/fetch-vs-axios-js-for-making-http-requests-2b261cdd3af5
Answer relevant as of January 2023
Many helpful answers here from 2018 are now outdated, the answer as of 2023 is to avoid mocking the store and instead use the real store, preferring integration tests (still using jest) over unit tests.
Some highlights from the updated, official Redux testing documentation:
Prefer writing integration tests with everything working together. For a React app using Redux, render a with a real store instance wrapping the components being tested. Interactions with the page being tested should use real Redux logic, with API calls mocked out so app code doesn't have to change, and assert that the UI is updated appropriately.
Do not try to mock selector functions or the React-Redux hooks! Mocking imports from libraries is fragile, and doesn't give you confidence that your actual app code is working.
It goes on to state how to achieve this, with the renderWithProvider function detailed here.
The article it links to for reasoning on this, includes the following quote, explaining the evolution of the thinking of redux testing best practices:
Our docs have always taught the "isolation" approach, and that does especially make sense for reducers and selectors. The "integration" approach was in a minority.
But, RTL and Kent C Dodds have drastically changed the mindset and approach for testing in the React ecosystem. The patterns I see now are about "integration"-style tests - large chunks of code, working together, as they'd be used in a real app.

Return value of a mocked function does not have `then` property

I have the following async call in one of my React components:
onSubmit = (data) => {
this.props.startAddPost(data)
.then(() => {
this.props.history.push('/');
});
};
The goal here is to redirect the user to the index page only once the post has been persisted in Redux (startAddPost is an async action generator that sends the data to an external API using axios and dispatches another action that will save the new post in Redux store; the whole thing is returned, so that I can chain a then call to it in the component itself). It works in the app just fine, but I'm having trouble testing it.
import React from 'react';
import { shallow } from 'enzyme';
import { AddPost } from '../../components/AddPost';
import posts from '../fixtures/posts';
let startAddPost, history, wrapper;
beforeEach(() => {
startAddPost = jest.fn();
history = { push: jest.fn() };
wrapper = shallow(<AddPost startAddPost={startAddPost} history={history} />);
});
test('handles the onSubmit call correctly', () => {
wrapper.find('PostForm').prop('onSubmit')(posts[0]);
expect(startAddPost).toHaveBeenLastCalledWith(posts[0]);
expect(history.push).toHaveBeenLastCalledWith('/');
});
So I obviously need this test to pass, but it fails with the following output:
● handles the onSubmit call correctly
TypeError: Cannot read property 'then' of undefined
at AddPost._this.onSubmit (src/components/AddPost.js:9:37)
at Object.<anonymous> (src/tests/components/AddPost.test.js:25:46)
at process._tickCallback (internal/process/next_tick.js:109:7)
So how can I fix this? I suspect this is a problem with the test itself because everything works well in the actual app. Thank you!
Your code is not testable in the first place. You pass in a callback to the action and execute it after saving the data to the database like so,
export function createPost(values, callback) {
const request = axios.post('http://localhost:8080/api/posts', values)
.then(() => callback());
return {
type: CREATE_POST,
payload: request
};
}
The callback should be responsible for the above redirection in this case. The client code which uses the action should be like this.
onSubmit(values) {
this.props.createPost(values, () => {
this.props.history.push('/');
});
}
This makes your action much more flexible and reusable too.
Then when you test it, you can pass a stub to the action, and verify whether it is called once. Writing a quality, testable code is an art though.
The problem with your code is that the startAddPost function is a mock function which does not return a Promise, but your actual this.props.startAddPost function does return a Promise.
That's why your code works but fails when you try to test it, leading to the cannot read property.... error.
To fix this make your mocked function return a Promise like so -
beforeEach(() => {
startAddPost = jest.fn().mockReturnValueOnce(Promise.resolve())
...
});
Read more about mockReturnValueOnce here.

Resources