How to mock Axios service wrapper using JEST - reactjs

I'm using JEST testing framework to write test cases for my React JS application. I'm using our internal axios wrapper to make service call. I would like to mock that wrapper service using JEST. Can someone help me on this ?
import Client from 'service-library/dist/client';
import urls from './urls';
import { NODE_ENV, API_VERSION } from '../screens/constants';
const versionHeader = 'X-API-VERSION';
class ViewServiceClass extends Client {
getFiltersList(params) {
const config = {
method: urls.View.getFilters.requestType,
url: urls.View.getFilters.path(),
params,
headers: { [versionHeader]: API_VERSION },
};
return this.client.request(config);
}
const options = { environment: NODE_ENV };
const ViewService = new ViewServiceClass(options);
export default ViewService;
Above is the Service Implementation to make API call. Which I'm leveraging that axios implementation from our internal library.
getFiltersData = () => {
const params = {
filters: 'x,y,z',
};
let {
abc,
def,
ghi
} = this.state;
trackPromise(
ViewService.getFiltersList(params)
.then((result) => {
if (result.status === 200 && result.data) {
const filtersJson = result.data;
.catch(() =>
this.setState({
alertMessage: 'No Filters Data Found. Please try after some time',
severity: 'error',
showAlert: true,
})
)
);
};
I'm using the ViewService to get the response, and I would like to mock this service. Can someone help me on this ?

You would need to spy your getFiltersList method from ViewServiceClass class.
Then mocking some response data (a Promise), something like:
import ViewService from '..';
const mockedData = {
status: 'ok',
data: ['some-data']
};
const mockedFn = jest.fn(() => Promise.resolve(mockedData));
let getFiltersListSpy;
// spy the method and set the mocked data before all tests execution
beforeAll(() => {
getFiltersListSpy = jest.spyOn(ViewService, 'getFiltersList');
getFiltersListSpy.mockReturnValue(mockedFn);
});
// clear the mock the method after all tests execution
afterAll(() => {
getFiltersListSpy.mockClear();
});
// call your method, should be returning same content as `mockedData` const
test('init', async () => {
const response = await ViewService.getFiltersList();
expect(response).toEqual(mockedData);
});
P.D: You can pass params to the method, but you will need to configure as well the mockedData as you wish.

Related

CyPress unable to mock API call

We are working on a react + typescript project & trying to implement unit testing using cypress.
I have a component called GettingStartedForm where two APIs are called on componentDidMount
Create API
Get API - is called once the creation is successful.
We are trying to mock both API calls but the Get API call is not getting mocked.
Here is the component,
componentDidMount() {
this.createMethod();
}
/* Create Method */
createMethod() {
const contentType = 'application/json';
const pathAPICreate = '/v1/create/';
postData(pathAPICreate, contentType, '')
.then((response:any)=> {
this.getData();
}).catch((error:any)=>{
...
});
}
/* Get step data */
async getData() {
const contentType = 'application/json';
const pathData = '/v1/data/';
try {
const dataResponse = await getCreatedData(
pathData,
contentType,
);
...
}
Here is my test,
import GettingStartedForm from '../GettingStartedForm';
import React from 'react';
import {mount} from '#cypress/react';
describe('Onboarding getting started form - step 1', () => {
beforeEach(() => {
cy.intercept('POST', '**/v1/create', {data: 'success'}).as('createConsultant');
cy.intercept('GET', '**/v1/data/', {fixture: 'gettingStartedStepResponse.json'}).as('stepData');
});
it('Should render all elements', () => {
mount(<GettingStartedForm
nextFormHandler={(e, formStatus) => false}
previousFormHandler={(e, formStatus) => false}
></GettingStartedForm>);
...
});
});
and console message here
Is there something we are missing here? Any help is highly appreciated.

Mock axios create in test file. React, typescript

I've seen some similar posts about mocking axios but I have spend some hours and I didn't manage to solve my problem and make my test work. I've tried solutions that I have found but they didn't work.
I'm writing small app using React, Typescript, react-query, axios. I write tests with React Testing Library, Jest, Mock Service Worker.
To test delete element functionality I wanted just to mock axios delete function and check if it was called with correct parameter.
Here is the PROBLEM:
I'm using axios instance:
//api.ts
const axiosInstance = axios.create({
baseURL: url,
timeout: 1000,
headers: {
Authorization: `Bearer ${process.env.REACT_APP_AIRTABLE_API_KEY}`,
},
//api.ts
export const deleteRecipe = async (
recipeId: string
): Promise<ApiDeleteRecipeReturnValue> => {
try {
const res = await axiosInstance.delete(`/recipes/${recipeId}`);
return res.data;
} catch (err) {
throw new Error(err.message);
}
};
});
//RecipeItem.test.tsx
import axios, { AxiosInstance } from 'axios';
jest.mock('axios', () => {
const mockAxios = jest.createMockFromModule<AxiosInstance>('axios');
return {
...jest.requireActual('axios'),
create: jest.fn(() => mockAxios),
delete: jest.fn(),
};
});
test('delete card after clicking delete button ', async () => {
jest
.spyOn(axios, 'delete')
.mockImplementation(
jest.fn(() =>
Promise.resolve({ data: { deleted: 'true', id: `${recipeData.id}` } })
)
);
render(
<WrappedRecipeItem recipe={recipeData.fields} recipeId={recipeData.id} />
);
const deleteBtn = screen.getByRole('button', { name: /delete/i });
user.click(deleteBtn);
await waitFor(() => {
expect(axios.delete).toBeCalledWith(getUrl(`/recipes/${recipeData.id}`));
});
});
In test I get error "Error: Cannot read property 'data' of undefined"
However if I would not use axios instance and have code like below, the test would work.
//api.ts
const res = await axios.delete(`/recipes/${recipeId}`);
I'm pretty lost and stuck. I've tried a lot of things and some answers on similar problem that I've found on stackoverflow, but they didn't work for me. Anybody can help?
I don't want to mock axios module in mocks, only in specific test file.
I don't have also experience in Typescript and testing. This project I'm writing is to learn.
I found some workaround and at least it's working. I moved axiosInstance declaration to a separate module and then I mocked this module and delete function.
//RecipeItem.test.tsx
jest.mock('axiosInstance', () => ({
delete: jest.fn(),
}));
test('delete card after clicking delete button and user confirmation', async () => {
jest
.spyOn(axiosInstance, 'delete')
.mockImplementation(
jest.fn(() =>
Promise.resolve({ data: { deleted: 'true', id: `${recipeData.id}` } })
)
);
render(
<WrappedRecipeItem recipe={recipeData.fields} recipeId={recipeData.id} />
);
const deleteBtn = screen.getByRole('button', { name: /delete/i });
user.click(deleteBtn);
await waitFor(() => {
expect(axiosInstance.delete).toBeCalledWith(`/recipes/${recipeData.id}`);
});
});
If you have a better solution I would like to see it.

Mocking cross-fetch

I'm running tests via node with Jest, on a Next/React project.
I'm also using cross-fetch as well.
When I try to mock cross-fetch for my component
import crossFetch from 'cross-fetch'
jest.mock('cross-fetch')
crossFetch.mockResolvedValue({
status: 200,
json: () => {{
user : testUser
}},
})
render(<UserProfile />)
The API request in the getServerSideProps
always returns 500
export async function getServerSideProps({ query: { userId } }) {
let user = null
let code = 200
try {
let response = await fetch(`https://example.com/users/${userId}`, { method: 'GET' })
let statusCode = response.status
let data = await response.json()
if (statusCode !== 200) {
code = statusCode
} else {
user = data.user
}
} catch (e) {
console.log(e.message)
}
return {
props: {
user,
code,
},
}
}
I have a feel it has something to do with the tests being initiating from Node and testing-library library is simulating a browser, that the actual lib making the request is not being mocked for the correct execution environment (browser in my case). But I'm not entirely sure.
Thanks in advance
Perhaps it's not working because of the default export being a function. Try this:
//test.js
import crossFetch from 'cross-fetch';
jest.mock('cross-fetch', () => {
//Mock the default export
return {
__esModule: true,
default: jest.fn()
};
});
test('should do a mock fetch', () => {
crossFetch.mockResolvedValue({
status: 200,
json: () => {{
user: testUser
}},
});
expect(crossFetch().status).toEqual(200);
});
I make use of fetch-mock (https://www.wheresrhys.co.uk/fetch-mock/) when testing fetch calls. You may need to provide a polyfill for fetch to be understood in the context of Jest tests, and this can be done with an import "cross-fetch/polyfill"; in the file where fetch is being used.
Note that the Create React App generated environment handles the necessary polyfill imports, but I'm not sure if Next.js has something similar.

How can I do a jest test in this function in React axios?

I guys I created a service in React and I need to test this part of the service, I'm using axios and Jest to do this.
I have the next code in React :
import axios from 'axios';
import Endpoints from './endpoints';
const baseUrl = Endpoints.getBackendEndpoint();
export const validateName = (nameObject, callback) => {
axios.post(`${baseUrl}/validateName`, {...nameObject})
.then(response =>{
response.data
})
.then(data => callback(data));
};
I don't need return the promise because all the work is doing by the callback() function.
This is the code that I have in Jest:
mport moxios from 'moxios';
import * as service from '../service';
import mockResponses from './service.test.json';
import Endpoints from '../endpoints';
const validateObjName = {
Id: 1,
Name: 'Bob',
}
beforeEach(() => {
const baseUrl = Endpoints.getBackendEndpoint();
moxios.stubRequest(
`${baseUrl}/validateName`,
{ ...validateObjName },
{
status: 200,
response: mockResponses.validateForm,
}
);
});
afterEach(() => {
moxios.uninstall();
});
it('validateName()', () => {
service.validateName(validateObjName, jest.fn());
});
It works, but still need to increase the Branch coverage.
Thanks for you help guys :D
To get code coverage the code has to run while a test is running so you will want to return the Promise so you can await it in your test so the then callbacks run during your test.
Also, you can simplify validateName to this:
import axios from 'axios';
import Endpoints from './endpoints';
const baseUrl = Endpoints.getBackendEndpoint();
export const validateName = (nameObject, callback) => {
return axios.post(`${baseUrl}/validateName`, { ...nameObject })
.then(response => callback(response.data));
};
In your test you need to install moxios in your beforeEach and pass the mock response as the second parameter to moxios.stubRequest.
Then use an async test function and await the Promise returned by validateName:
import moxios from 'moxios';
import * as service from '../service';
import mockResponses from './service.test.json';
import Endpoints from '../endpoints';
const validateObjName = {
Id: 1,
Name: 'Bob',
}
beforeEach(() => {
moxios.install(); // install moxios
const baseUrl = Endpoints.getBackendEndpoint();
moxios.stubRequest(
`${baseUrl}/validateName`,
{
status: 200,
response: mockResponses.validateForm
}
); // response is the second argument
});
afterEach(() => {
moxios.uninstall();
});
it('validateName()', () => {
service.validateName(validateObjName, jest.fn());
});
it('validateName()', async () => { // use an async test function
const spy = jest.fn();
await service.validateName(validateObjName, spy); // await the Promise
expect(spy).toHaveBeenCalledWith(mockResponses.validateForm); // Success!
});
That should give you a working test and 100% code coverage.

Mocking a HttpClient with Jest

I'm having trouble with my React Native + Jest + Typescript setup.
I'm trying to test a thunk/network operation. I've created a networkClient function:
export const networkClient = async (
apiPath: string,
method = RequestType.GET,
body = {},
authenticate = true,
appState: IAppState,
dispatch: Dispatch<any>
) => {
... validate/renew token, validate request and stuff...
const queryParams = {
method,
headers: authenticate
? helpers.getHeadersWithAuth(tokenToUse)
: helpers.getBaseHeaders(),
body: method === RequestType.POST ? body : undefined,
};
const fullUri = baseURL + apiPath;
const result = await fetch(fullUri, queryParams);
if (result.ok) {
const json = await result.json();
console.log(`Result ${result.status} for request to ${fullUri}`);
return json;
} else {
... handle error codes
}
} catch (error) {
handleNetworkError(error, apiPath);
}
};
Now, when writing my tests for the operations which uses the networkClient above to request server data like so:
const uri = `/subscriptions/media` + tokenParam;
const json = await networkClient(
uri,
RequestType.GET,
undefined,
true,
getState(),
dispatch
);
I'd like to mock the implementation to return a mock response pr. test.
As pr the docs, I thought this could be done like so:
import { RequestType, networkClient} from './path/to/NetworkClient';
and in the test:
networkClient = jest.fn(
(
apiPath: string,
method = RequestType.GET,
body = {},
authenticate = true,
appState: IAppState,
dispatch: Dispatch<any>
) => {
return 'my test json';
}
);
const store = mockStore(initialState);
return store
.dispatch(operations.default.getMoreFeedData(false))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(store.getState().feedData).toEqual(testFeed);
// fetchMock.restore();
});
but networkClient is not defined, and ts tells me
[ts] Cannot assign to 'networkClient' because it is not a variable.
What did I get wrong? I must have missed something about how Jest mocks modules and how to provide a mock implementation somewhere, but I can't find it on neither the docs, nor on Google/SO.
Any help is much appreciated
So I found the solution
The import should not be
import { RequestType, networkClient} from './path/to/NetworkClient';
but instead the module should be required like so:
const network = require('./../../../../networking/NetworkClient');
After that, i could successfully mock the implementation and complete the test:
const testFeed = { items: feed, token: 'nextpage' };
network.networkClient = jest.fn(() => {
return testFeed;
});
I hope it helps someone

Resources