How to test axios get request function in jest/enzyme tests? - javascript

I have follow React component which render as a child in parent component and props are passed:
<Component
localStorageValue={'test-walue'}
requestDataFunc={getData}
requestUserData={getUserData}
expectedResponseKey={'key'}
dataType={'test}
activePage={'index'}
saveData={this.setData}
/>
so requestDataFunc is a funtion which passed to component and runned in componentDidMount :
componentDidMount() {
requestDataFunc().then(({ data }) => {
const { selectedDataItems } = this.state;
const expectedResponseData = data[expectedResponseKey];
let interimDataArr = [];
expectedResponseData.forEach((item) => {
interimDataArr = [...interimDataArr, {
...item,
active: selectedDataItems.length ? selectedDataItems.some((selectedItemId) => selectedItemId === item.id) : false,
}];
});
}
but when I run my tests, I got:
TypeError: Cannot read property 'then' of undefined
requestDataFunc().then(({ data }) => {
const { selectedDataItems } = this.state;
const expectedResponseData = data[expectedResponseKey];
let interimDataArr = [];
I just starting to test render component:
describe('correct component render', () => {
const defaultProps = {
localStorageValue: 'test-walue',
requestDataFunc: jest.fn(),
requestUserData: jest.fn(),
expectedResponseKey: 'key',
dataType: 'test',
activePage: 'index',
saveData: jest.fn(),
};
const wrapper = shallow(<ComponentName { ...defaultProps } />);
test("render component", () => {
expect(wrapper.length).toEqual(1);
});
});
I suppose that I need to mock somehow request and data that this request should receive. How to do this correctly?

Have you tried mocking promise as below:
var mockPromise = new Promise((resolve, reject) => {
resolve(<mock response similar to actual promise response>);
});
describe('correct component render', () => {
const defaultProps = {
localStorageValue: 'test-walue',
requestDataFunc: jest.fn().mockReturnValueOnce(mockPromise),
requestUserData: jest.fn(),
expectedResponseKey: 'key',
dataType: 'test',
activePage: 'index',
saveData: jest.fn(),
};
const wrapper = shallow(<ComponentName { ...defaultProps } />);
test("render component", () => {
expect(wrapper.length).toEqual(1);
});
});

Axios get method returns a promise, so you when you mock that method you also need to return a Promise
jest.fn(() => Promise.resolve({}))
More on Jest async mocking
Relevant answer from SO

Related

MockReturnValue is returning undefined on axios wrapper function

I am trying to test my componentDidMount and ensure that this axiosRequest returns an array. However no matter how I mock my axiosRequest function, it always returns undefined. What am I missing??
My axiosRequest wrapper fn:
export const axiosRequest = (type, url, body, headers) => {
return axios[type](url,
{
...body,
sharedLinkToken: store.getState()?.token ? store.getState()?.token : null,
activeWorkspace: body?.activeWorkspace ? body?.activeWorkspace :
store.getState()?.auth?.org?.activeWorkspace,
},
{ ...headers },
).then((res) => res?.data);
};
Calling said function in my CDM
async componentDidMount() {
try {
const fieldTypes = await axiosRequest('post', '/api/custom-fields/types');
console.log('fieldTypes: ', fieldTypes);
this.setState({ fieldTypes });
} catch (e) {
this.setState({ disable: true });
}
}
My test with its imports with my various ways to mock this function:
import * as axiosRequest from '../../../../utilities/utilities';
let mockAxiosRequest;
// jest.mock('axios');
// jest.mock('../../../../utilities/utilities', () => ({
// axiosRequest: jest.fn(),
// }));
beforeEach(() => {
props = {};
wrapper = shallow(<CreateCustomField {...props} />);
instance = wrapper.instance();
mockAxiosRequest = jest.spyOn(axiosRequest, 'axiosRequest');
});
it('Should find the wrapper', async () => {
mockAxiosRequest.mockResolvedValue(['stuff']);
// mockAxiosRequest.mockResolvedValue(() => ['stuff']);
// mockAxiosRequest.mockResolvedValueOnce(['stuff']);
// mockAxiosRequest.mockReturnThis(['stuff']);
// mockAxiosRequest.mockImplementation(() => ['stuff']);
// mockAxiosRequest.mockImplementation(() => Promise.resolve(['stuff']));
expect(wrapper.find('.create-custom-fields-modal').length).toBe(1);
expect(instance.state.fieldTypes.length).toBe(1);
});
I found that my mocks were working fine, however the componentDidMount was the issue. So because of this, I am doing everything manually, and everything is working as expected.
beforeEach(() => {
props = {};
mockAxiosRequest = jest.spyOn(axiosRequest, 'axiosRequest');
wrapper = shallow(<CreateCustomField {...props} />, { disableLifecycleMethods: true });
instance = wrapper.instance();
instance.setState({ fieldTypeOptions: [{ persistent_id: '123',field_type: 'Date & Time' }] });
});
Then my two tests that are now passing:
it('Should find the wrapper', async () => {
expect(wrapper.find('.create-custom-fields-modal').length).toBe(1);
});
it('Should call componentDidMount', async () => {
mockAxiosRequest.mockResolvedValueOnce([
{ persistent_id: '456', field_type: 'Date & Time' },
{ persistent_id: '123', field_type: 'Number' },
]);
await instance.componentDidMount();
expect(instance.state.fieldTypeOptions.length).toBe(2);
});

Getting error when try to mock Auth.currentSession(). What I'm doing wrong?

Place where I'm using current session:
axios.interceptors.request.use(async (config) => {
await cognitoConfig();
const session = await Auth.currentSession();
const token = session.idToken.jwtToken;
if (config?.headers?.common) {
config.headers.common.Authorization = `Bearer ${token}`;
}
return config;
});
In my test I'm trying to mock Auth.currentSession() but always getting the same error:
Cannot read properties of undefined (reading 'idToken')
TypeError: Cannot read properties of undefined (reading 'idToken')
My code below:
jest.spyOn(Auth, 'currentSession').mockImplementation(() => ({
getIdToken: () => ({
getJwtToken: () => 'mock'
})
}));
What I'm doing wrong?
All my test:
import { screen } from '#testing-library/react';
import { Auth } from 'aws-amplify';
import { render } from '../../../../config/test/root-render';
import PageProvider from '../PageContext';
import Status from './Status';
const path = '/path';
const pageName = 'Page';
jest.mock('../../../../root/router', () => {
return {
generateUrlByKey: () => {
return path;
},
useCurrentRoute: () => ({
parent: {},
params: {},
name: pageName
})
};
});
jest.spyOn(Auth, 'currentSession').mockImplementation(() => ({
getIdToken: () => ({
getJwtToken: () => 'mock'
})
}));
describe('<Status />', () => {
it('Should verify that all general elements of component exist', async () => {
render(
<PageProvider>
<Status status="OPEN" isAdmin={true} />
</PageProvider>
);
const statusTitle = await screen.findByText('Status:');
const currentStatus = await screen.findByText('OPEN');
const editButton = await screen.findByText('edit');
expect(statusTitle).not.toBeUndefined();
expect(currentStatus).not.toBeUndefined();
expect(editButton).not.toBeUndefined();
});
});

Mocking axios with Jest throws error “Cannot read property 'interceptors' of undefined”

I’m having trouble mocking axios with Jest and react-testing-library. I’m stuck on an error around axios interceptors and can’t make my way around it.
This is my api.js file:
import axios from 'axios';
const api = axios.create({
baseURL: window.apiPath,
withCredentials: true,
});
api.interceptors.request.use(config => {
const newConfig = Object.assign({}, config);
newConfig.headers.Accept = 'application/json';
return newConfig;
}, error => Promise.reject(error));
The call to api in my component:
const fetchAsync = async endpoint => {
const result = await api.get(endpoint);
setSuffixOptions(result.data.data);
};
Then in my spec file:
jest.mock('axios', () => {
return {
create: jest.fn(),
get: jest.fn(),
interceptors: {
request: { use: jest.fn(), eject: jest.fn() },
response: { use: jest.fn(), eject: jest.fn() },
},
};
});
test('fetches and displays data', async () => {
const { getByText } = render(<Condition {...props} />);
await expect(getByText(/Current milestone/i)).toBeInTheDocument();
});
The test fails with this message:
TypeError: Cannot read property 'interceptors' of undefined
6 | });
7 |
> 8 | api.interceptors.request.use(config => {
| ^
9 | const newConfig = Object.assign({}, config);
10 | newConfig.headers.Accept = 'application/json';
11 |
What am I doing wrong here?
the create method is what creates the api which has the get and interceptors methods. So you need to create a dummy api object:
jest.mock('axios', () => {
return {
create: jest.fn(() => ({
get: jest.fn(),
interceptors: {
request: { use: jest.fn(), eject: jest.fn() },
response: { use: jest.fn(), eject: jest.fn() }
}
}))
}
})
You must mock api.js, not Axios.
import { FuncToCallAPI } from './funcAPI';
import api from './api';
describe('Mock api.ts', () => {
it('should get data', async () => {
const Expected = { status: 200, data: {} };
jest.spyOn(api, 'get').mockResolvedValue(Expected);
const result = await FuncToCallAPI('something');
expect(result).toEqual(Expected);
});
});
Then create a test for api.js, mocking the axios.create as you did before.

Trouble mocking a function in a module

I'm trying to create a simple test for a module. I got some firestore triggers in a module (see the module file down below). In the onDelete trigger I want to just test to see if the deleteColletion is called. To do that I need to mock out just the deleteCollection function. In my test (see onDelete should also delete the sub-collections trails and downloads in the test file) I mock the deleteCollection function and call the firestore trigger and checks if deleteCollection is called. This is the failing response I get from the test:
Error: expect(jest.fn()).toBeCalled()
Expected number of calls: >= 1
Received number of calls: 0
It seems like jest don't match one the function I mock. What am I doing wrong?
NB! I now that this test in it self is not a good test ;)
Test file
const functions = require('firebase-functions-test');
const admin = require('firebase-admin');
const triggers = require("../../data/protocol/triggers");
const {createEvent} = require("../test_utilities");
const testEnv = functions();
const mockUpdate = jest.fn();
mockUpdate.mockReturnValue(true);
jest.mock("firebase-admin", () => ({
initializeApp: jest.fn(),
firestore: () => ({
batch: jest.fn(() => ({commit: jest.fn()})),
collection: () => (
{
doc: () => ({update: mockUpdate}),
orderBy: () => ({
limit: jest.fn(() => ({
get: jest.fn(() => ({
size: 0,
docs: {
forEach: jest.fn(),
}
}))
}))
})
}
)
}),
})
);
jest.mock('../../data/protocol/triggers', () => ({
...(jest.requireActual('../../data/protocol/triggers')),
deleteCollection: jest.fn(() => [])
})
);
describe("Protocol trigger", () => {
let adminStub, triggersStub, api;
const context = {
params: {
protocolId: 0,
}
};
beforeAll(() => {
adminStub = jest.spyOn(admin, "initializeApp");
//triggersStub = jest.spyOn(triggers, 'deleteCollection');
api = require("../../index");
});
beforeEach(() => jest.clearAllMocks());
afterAll(() => {
adminStub.mockRestore();
//triggersStub.mockRestore();
testEnv.cleanup();
});
...
it('`onDelete` should also delete the sub-collections `trails` and `downloads`', async () =>
{
const onDeleteProtocol = testEnv.wrap(api.onDeleteProtocol);
const event = {id: 0};
await onDeleteProtocol(event, {});
expect(triggers.deleteCollection).toBeCalled();
expect(onDeleteProtocol(event, {})).toBe([]);
});
});
Module
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const {lastModifiedNeedsUpdate} = require("../utilities/event");
function deleteCollection(db, collectionPath, batchSize) {
return deleteQueryBatch(db, db.collection(collectionPath).orderBy('__name__').limit(batchSize), batchSize, []);
}
...
const deleteProtocol = () => functions.firestore
.document('protocols/{protocolId}')
.onDelete((event, context) => {
return deleteCollection(admin.firestore(), `protocols/${event.id}/trails`, 1);
});
module.exports = {
deleteProtocol,
createProtocol,
updateProtocol,
deleteCollection,
};
-- Frode
I resolved this by moving the helper functions (deleteCollection and deleteQueryBatch) into it's own module and mock that module.
--
Frode

react state is not updating in jest

In my react component I have two functions. handleChangeInput(e) is called on 'OnChange' of input field and checkFields() is called from handleChangeInput(e)
constructor(){
super()
this.state={
email: '',
password:'',
validFields: false
}
}
handleChangeInput(e){
const name = e.target.name;
const value = e.target.value;
this.setState({[name]: value},()=>{
this.checkFields();
});
}
checkFields(){
if (this.state.email.length>0 && this.state.password.length>0 ) {
this.setState({validFields: true});
}else {
this.setState({validFields: false});
}
}
And in my index.test.js I have
describe('<Login />', () => {
describe('handleChangeInput', () => {
const component = new Login()
const wrapper = shallow(<Login />);
beforeEach(() => {
component.setState = jest.fn()
})
test('calls setState validFields false when no email/password', () => {
const state = { state : { email: '', password: ''} }
const args = { target : { name: 'name', value: 'value' } }
component.handleChangeInput.call(state, args)
expect(component.setState.mock.calls.length).toBe(1)
expect(wrapper.state().validFields).toEqual(false)
})
test('calls setState validFields true when email/password are ok', () => {
const state = { state : { email: 'email', password: 'password' } }
const args = { target : { name: 'name', value: 'value' } }
component.handleChangeInput.call(state, args)
expect(component.setState.mock.calls.length).toBe(1)
expect(wrapper.state().validFields).toEqual(false)
})
})
});
But my state is not being updated. As a result, 'validFields' is not set to true and my second test is failing. I tried wrapper.update() and wrapper.instance().forceUpdate() but still no success. Any help would be appreciated
I am guessing it might be because you override the setState function with jest.fn()
component.setState = jest.fn()
})
how about removing this?
hope my answer does not come too late, but you are trying to update the state in a wrong way.
First of all, remove these two:
const component = new Login()
beforeEach(() => {
component.setState = jest.fn()
})
And most likely you want to change this:
handleChangeInput(e){
const name = e.target.name;
const value = e.target.value;
this.setState({[name]: value},()=>{
this.checkFields();
});
}
handleChangeInput(e){
const name = e.target.name;
const value = e.target.value;
this.setState(()=>{
return { email: name}
});
this.setState(()=>{
return { password: value }
});
this.checkFields();
}
const component = new Login() does not bring any value to this test and you should not mock the setState if you want that it's actually changed.
Instead you should test the actual component (like you partially already do here)
Change the code like this:
test('calls setState validFields true when email/password are ok', () => {
const args = { target : { email: 'email', password: 'password' } }
wrapper.instance().handleChangeInput(args)
expect(wrapper.state('email')).toEqual('email')
expect(wrapper.state('password')).toEqual('password')
expect(wrapper.state('validFields')).toBeTruthy()
})
I found this answer in one of the git forums. It worked for me.
// somewhere in your test setup code
global.flushPromises = () => {
return new Promise(resolve => setImmediate(resolve))
}
test('something with unreachable promises', () => {
expect.hasAssertions()
const component = mount(<Something />)
// do something to your component here that waits for a promise to return
return flushPromises().then(() => {
component.update() // still may be needed depending on your implementation
expect(component.html()).toMatchSnapshot()
})
})

Categories

Resources