Mocking cross-fetch - reactjs

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.

Related

getting issue in jest unit testing for login

i have created login page,which works fine, when i created unit test for that module with jest, i am getting issue, it doesn't return promiss when login api calls, here is my code for react and jest respectively, i am not getting console for console.log("after login"); can anyone please check my code and help to resolve this issue ?
validateAll(formData, rules, message).then(async () => {
dispatch(setLoading(true))
console.log("before login");
const login = await authApi.login(app, email, password)
console.log("after login");
if (login && login.error && login.error !== null) {
dispatch(setLoading(false))
ToastAlert({ msg: login.error.error || login.error.message, msgType: 'error' });
dispatch(setToken(''))
} else {
console.log("done");
dispatch(setToken(login.data._accessToken))
setSuccess(true)
dispatch(setLoading(false))
//router.replace("/");
ToastAlert({ msg: 'You have successfully logged in', msgType: 'success' });
}
}
auth
import { resolve } from "./resolve";
import * as Realm from "realm-web";
function auth() {
const register = async (app: any, data: object) => {
return await resolve(
app.emailPasswordAuth.registerUser(data)
.then((response: any) => response)
)
}
const login = async (app: any, email: string, password: string) => {
const credentials = await Realm.Credentials.emailPassword(email, password);
return await resolve(
app.logIn(credentials)
.then((response: any) => response)
)
}
const confirmUser = async (app: any, data: object) => {
return await resolve(
app.emailPasswordAuth.confirmUser(data)
.then((response: any) => response)
)
}
const logout = async (app: any) => {
return await resolve(
app.currentUser.logOut()
.then((response: any) => response)
)
}
return {
register,
login,
confirmUser,
logout
}
}
const authApi = auth();
export default authApi;
unit test
test('login with correct username and password', async () => {
const initialState = {
email: '',
password: '',
error: {
email: "",
password: ""
},
};
const mockStore = configureStore();
let store;
store = mockStore(initialState);
const { getByText,getByLabelText } = render(
<Provider store={store}>
<Authenticate />
</Provider>
);
// fill out the form
fireEvent.change(screen.getByLabelText('Email'), {
target: {value: '***#gmail.com'},
})
fireEvent.change(screen.getByLabelText(/Parola/i), {
target: {value: '****#123'},
})
const loginAwait = screen.getByText(/Authentificare/i);
await fireEvent.click(loginAwait)
// wait for the error message
const alert = await screen.findByRole('alert')
expect(alert).toHaveTextContent(/You have successfully logged in/i)
})
As mentioned in the comments you can't make real requests in jest. You have 2 big options to solve your issue:
Option 1.1: As was mentioned in the comment (and shown in the link) you can mock your request library (in your case is not axios, is Realm.Credentials.emailPassword, but you probably also have to mock the app.logIn part) :
Just replace Realm, for example, you should add something like this to your unit test:
...
const Realm = require("realm-web");
jest.mock("realm-web")
...
test('login with correct username and password', async () => {
Realm.Credentials.emailPassword.mockResolvedValue({token: "test-token"})
})
WARNING: As mentioned above this by itself most likely won't fix your problem since you have to also mock app.logIn (however assuming app.logIn is just calling under the hood Realm.App.logIn you might be able to mock that too by adding:
Realm.App.logIn.mockResolvedValue({user: {...})
(Realm.App might need to be Realm.App())
If Realm.Credentials.emailPassword throws an error you might need to define them first when defining jest.mock("realm-web"). So something like :
jest.mock("realm-web", () => ({
App: (config) => {
return {
logIn: jest.fn()
}
},
Credentials: {
emailPassword: jest.fn()
}
}))
or you can just mock the library at the beginning using something like:
jest.mock("realm-web", () => ({
App: (config) => {
return {
logIn: (token:string) => ({user: {...}})
}
},
Credentials: {
emailPassword: (email: string, password:string) => ({token: "test-token"})
}
}))
)
If this is not the case you need to figure how to mock that as well (is kind of hard to properly fix your issue without a working example). But assuming you are doing something like app = new Realm.App(...) you might want to check how to mock new Function() with Jest here. If you get to this, you will most likely need a hybrid solution (to mock both new Realm.App and Realm.Credentials.emailPassword)
You could also try to mock the entire module at once, at the beginning of the test file using something like:
jest.mock("realm-web", () => ({
App: (config) => {
return {
logIn: (token:string) => ({user: {...}})
}
},
Credentials: {
emailPassword: (email: string, password:string) => ({token: "test-token"})
}
}))
OBS: adjusments might be required. This is just an example.
Also please be aware that this will create a mock for all the tests following the execution of this code.
Option 1.2:
You could also use a similar strategy to mock your authApi (you have some examples on how to mock a default export here). Just make sure you mock the the login function from it (and if there is any other function from the api -> like confirmUser used on the same test mock that as well). This option would be easier to implement but it's really up to what you want your test to cover. Also a hybrid might be mocking the app part together with Realm.Credentials.emailPassword.
Option 2: You might find an existing solution. Here are some interesting links:
github Realm mock
react native example from mogoDB page -> this won't work copy-pasted, but might serve as inspiration.
Another maybe somehow related question would be How to fake Realm Results for tests. It is not answering your question but might also help a little.

Jest Mock not returning the mock data while writing Unit Test in react

I am calling an API using fetch and writing test cases for that. While making the Fetch call, I am expected mocked data but getting API error message.
Please help me to know why its not mocking the data.
Using Jest, jest-fetch-mock modules. Code is as follow
const login = (username, password) => {
return fetch('endpoint/login', () => {
method: 'POST',
body: JSON.stringify({
data :{
username,
password
}
})
})
.then (res => res.json())
.then (data => {
if(data.response){
return {
response: "accessToken",
error: null
}
}else{
return {
response: null,
error: "Something went wrong"
}
}
})
}
Now I am writing Unit Test to test this api, as below :-
test("Auth Success Scenario", async () => {
const onResponse = jest.fn();
const onError = jest.fn();
fetchMock.mockResponseONce(JSON.stringify({
data: {
response: "accessToken",
error: null
}
}));
return await AuthService.login("andy","password")
.then(onResponse)
.catch(onError)
.finally( () => {
console.log(onResponse.mock.calls[0][0]) // its return API error not mocked data
})
})
It was meant to be comment, sadly I don't have enough reputation.
Have you enabled jest mocks as specified in the documentation link
Create a setupJest file to setup the mock or add this to an existing setupFile. :
To setup for all tests
require('jest-fetch-mock').enableMocks()
Add the setupFile to your jest config in package.json:
"jest": {
"automock": false,
"setupFiles": [
"./setupJest.js"
]
}
Because that seems to be the only case, in which fetch will try to make actual call to the API, instead of giving mocked response, thus causing failure.
You can even enable mocks for specific test file as well
import fetchMock from "jest-fetch-mock";
require('jest-fetch-mock').enableMocks();

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.

How to mock Axios service wrapper using JEST

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.

Axios middleware to use in all instances of axios

I'm using axios in my react app using import axios from 'axios in many of my scripts. I want to use sort of a middleware that is invoked for all axios calls/errors. How do I approach this?
As per the documentation - You need to create a file i.e
// api-client.js
import axios from 'axios';
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
console.log(config);
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
export default axios;
Then from your container or controller, import above file:
// Home.js
import apiClient from './api-client.js';
Interceptors are the Axios way of doing this. For me though, it was too limited, tangled in Axios' API, difficult to test, etc.
Axios-middleware
So I wrote the axios-middleware module, a simple middleware service that hooks itself in your axios instance (either global or a local one) and provides a simple, self-contained and easily testable middleware API.
Note: it shines in bigger apps where minimal coupling is really important.
Simple example
Here's a simple example from the documentation
import axios from 'axios';
import { Service } from 'axios-middleware';
const service = new Service(axios);
service.register({
onRequest(config) {
console.log('onRequest');
return config;
},
onSync(promise) {
console.log('onSync');
return promise;
},
onResponse(response) {
console.log('onResponse');
return response;
}
});
console.log('Ready to fetch.');
// Just use axios like you would normally.
axios('https://jsonplaceholder.typicode.com/posts/1')
.then(({ data }) => console.log('Received:', data));
It should output:
Ready to fetch.
onRequest
onSync
onResponse
Received: {userId: 1, id: 1, title: ...
Testing a middleware
Say we have the following self-contained middleware class that we want to test.
export default class ApiErrorMiddleware {
constructor(toast) {
this.toast = toast;
}
onResponseError(err = {}) {
let errorKey = 'errors.default';
const { response } = err;
if (response && response.status) {
errorKey = `errors.${response.status}`;
} else if (err.message === 'Network Error') {
errorKey = 'errors.network-error';
}
this.toast.error(errorKey);
throw err;
}
}
Then it's really easy, we don't even need to mock Axios.
import ApiErrorMiddleware from '#/middlewares/ApiErrorMiddleware';
describe('ApiErrorMiddleware', () => {
let toast;
let middleware;
// Jest needs a function when we're expecting an error to be thrown.
function onResponseError(err) {
return () => middleware.onResponseError(err);
}
beforeEach(() => {
toast = { error: jest.fn() };
middleware = new ApiErrorMiddleware(toast);
});
it('sends a code error message', () => {
expect(onResponseError({ response: { status: 404 } })).toThrow();
expect(toast.error).toHaveBeenLastCalledWith('errors.404');
});
});

Resources