How to mock a fetch call that is within an arrow function? - reactjs

I'm trying to test the invocation of a function that deletes specific data saved in a database in React. The problem is I want to only mock the fetch call and have everything else run as usual because right now whenever tests are run the data gets deleted in the database.
Here is my code for the delete function:
deleteEvent = async () => {
try {
await fetch(
"api url",
{
method: "DELETE",
}
)
.then((res) => res.json())
.then(
(result) => {
console.log(result);
},
(error) => {
console.log(error);
}
);
} catch (error) {
console.error(error);
}
this.props.history.push("/eventList");
};
And here is my test code:
test("deleteEvent function works", (done) => {
const mockSuccessResponse = {};
const mockJsonPromise = Promise.resolve(mockSuccessResponse);
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
});
jest.spyOn(global, "fetch").mockImplementation(() => mockFetchPromise);
const historyMock = { push: jest.fn() };
const wrapper = shallow(<EventState history={historyMock} />);
wrapper.instance().deleteEvent();
expect(global.fetch).toHaveBeenCalledTimes(1);
expect(historyMock.push.mock.calls[0]).toEqual(["/eventList"]);
global.fetch.mockClear();
done();
});
I get number times called: 0 for the expect(global.fetch).toHaveBeenCalledTimes(1);
and a Received: undefined for the expect(historyMock.push.mock.calls[0]).toEqual(["/eventList"]);
Any help would be great

Instead of using spyOn(global, fetch), try this:
global.fetch = jest.fn().mockImplementation(() => mockFetchPromise);
const historyMock = { push: jest.fn() };
const wrapper = shallow(<EventState history={historyMock} />);
wrapper.instance().deleteEvent();
expect(global.fetch).toHaveBeenCalledTimes(1);
expect(historyMock.push.mock.calls[0]).toEqual(["/eventList"]);
global.fetch.mockClear();
done();
});

Related

How to test custom hooks with event listener inside useEffect?

I'm using react native and jest to create my tests. I'm facing problems to test an event listener that listens to url events from expo-linking. This event listenner is inside an useEffect hook.
Below is the code from my custom hook with my useEffect and an event listener inside:
useEffect(() => {
isMounted.current = true;
Linking.addEventListener('url', async () => {
try {
if (!navigation.isFocused() || !isMounted.current) return;
setIsLoading(true);
const response = await api.get('sessions/auth/success');
if (!response.data) return;
console.log('aqui');
const { notRegisteredUser, token } = response.data;
api.defaults.headers.authorization = `Bearer ${token}`;
if (notRegisteredUser && token) {
setIsLoading(false);
navigation.navigate('BirthDateScreen');
dispatch(
updateUser({
...notRegisteredUser,
}),
);
}
} catch (err) {
Alert.alert('Error', `${translate('loginRegisterError')}: `, err);
}
});
return () => {
isMounted.current = false;
};
}, [dispatch, navigation]);
In my test file I have the following mocks:
jest.mock('expo-linking', () => {
return {
addEventListener: (event: string, callback: () => void) => callback(),
};
});
jest.mock('#react-navigation/native', () => {
return {
useNavigation: () => ({
isFocused: mockedNavigationFocus,
navigate: mockedNavigation,
}),
};
});
jest.mock('react-redux', () => {
return {
useDispatch: jest.fn(),
};
});
jest.mock('../../../store/modules/user/actions', () => {
return {
updateUser: jest.fn(),
};
});
jest.mock('i18n-js', () => {
return {
locale: 'en',
t: () => 'any key',
};
});
Finally this is how my test looks in my first try:
it('should pass the test', async done => {
mockedNavigationFocus.mockImplementation(() => true);
apiMock.onGet('sessions/auth/success').reply(200, {
notRegisteredUser: { name: 'Logan' },
token: '123',
});
render(<LoginScreen />);
await waitFor(() =>
expect(mockedNavigation).toHaveBeenCalledWith('BirthDateScreen'),
);
done();
});
In my second try this is how my test looked (I used renderHooks from #testing-library/react-hooks):
it('should pass the test', async () => {
mockedNavigationFocus.mockImplementation(() => true);
apiMock.onGet('sessions/auth/success').reply(200, {
notRegisteredUser: { name: 'Logan' },
token: '123',
});
const { result, waitForValueToChange } = renderHook(() => useLoginButton());
const { isLoading } = result.current;
await waitForValueToChange(() => isLoading);
await waitForValueToChange(() => isLoading);
expect(mockedNavigation).toHaveBeenCalledWith('BirthDateScreen');
});
With both tests I get the following error:
test error
Another error I get is that my callback function inside useEffect runs many times before it stops and this does not happen when I am not testing.
Does anyone knows how can I write this test?

How can i test downloading excel file with jest?

I am new to testing react components with jest and enzyme. I have this example
generateExcelFile = () => {
const {actions, state} = this.props;
const dateFrom = state.getIn(['config', 'marketingQuestionReport', 'dateFrom']);
const dateTo = state.getIn(['config', 'marketingQuestionReport', 'dateTo']);
this.setState({isLoading: true, isLoadingFinished: false});
fetch(`${env.MARKETING_QUESTION_REPORT}?dateFrom=${dateFrom}&dateTo=${dateTo}`)
.then((resp) => resp.blob())
.then((blob) => {
if (typeof window.navigator.msSaveBlob !== 'undefined') {
window.navigator.msSaveBlob(
blob,
`Marketing Question Report from ${dateFrom} to ${dateTo}.xlsx`
);
}
const url = window.URL.createObjectURL(blob);
const tempLink = document.createElement('a');
tempLink.style.display = 'none';
tempLink.href = url;
tempLink.setAttribute(
'download',
`Marketing Question Report from ${dateFrom} to ${dateTo}.xlsx`
);
if (typeof tempLink.download === 'undefined') {
tempLink.setAttribute('target', '_blank');
}
document.body.appendChild(tempLink);
tempLink.click();
window.URL.revokeObjectURL(url);
toastr.success('Sucessfully generated marketing question report');
this.setState({isLoading: false, isLoadingFinished: true});
actions.clearMarketingQuestionReportDates();
})
.catch(() =>
toastr.error('An error occurred while generating marketing question report')
);
};
I am struck on .then part i don't know how to test after that the whole fetch call
What i have sofar
describe('<MarketingQuestionReportPage />', () => {
beforeEach(() => {
const fecthSpy = jest.spyOn(window, 'fetch').mockReturnValue(() =>
Promise.resolve({
blob: () =>
Promise.resolve({
size: 6682,
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
}),
})
);
});
test('generateExcelFile', () => {
const props = {
state: fromJS({
config: {
marketingQuestionReport: {
dateFrom: '2019-01-01',
dateTo: '2021-03-21',
},
},
}),
actions: {
clearMarketingQuestionReportDates: jest.fn(),
},
};
const tree = shallowSetup(props);
tree.instance().generateExcelFile();
tree.fecthSpy.toHaveBeenCalled();
});
});
i maked a instance of my component - i got prepaired the needed props for that call to work and i am sending them to my instance.After that i call my method - generateExcelFile();
On my coverage everything is freen except the fetch call. I don't know how to fix this. Please help
You need to create a spy of fetch before rendering the component with jest.spyOn(object, methodName):
test('generateExcelFile', () => {
const fetchSpy = jest.spyOn(window, "fetch").mockReturnValue(Promise.resolve({
blob: () =>
Promise.resolve({
size: 6682,
type:
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
}),
})
);
//...
const tree = shallowSetup(props);
tree.instance().generateExcelFile();
expect(fetchSpy).toHaveBeenCalled();
});
In your test you can check if fecthSpy have been called

Calling axios request one after the other?

I have tow functions in my ReactJs application called,
AuthService.addUser(newUser);
AuthService.userCategories(usercategories);
I want to run these two functions separately, which means the Axios request of the second function should be called after the Axios request of the first function when clicked the submit button. How do I approach the solution? Thanks in advance.
I tried in this way. Is this correct?
const handleSubmit = (e) => {
e.preventDefault();
AuthService.addUser(newUser);
AuthService.userCategories(usercategories);
};
Here are my two functions
addUser: (user) => {
//console.log(post);
axios
.post(CONSTANTS.HOSTNAME + "/api/users/register", user)
.then((res) => {
//save to local storage
const { token } = res.data;
localStorage.setItem("jwtToken", token);
isAuthenticated.next(true);
setAuthToken(token);
Swal.fire({
icon: "success",
title: "Signup Successful!",
showConfirmButton: false,
timer: 1500,
}).then(() => {
window.location.href = "/";
//decode token to get user data
const decoded = jwt_decode(token);
currentUser.next(decoded);
console.log(decoded);
});
})
.catch((err) => {
console.log(err.response.data);
Swal.fire({
icon: "error",
title: "Oops...",
text: err.response.data,
});
// alert(JSON.stringify(err.response.data));
});
},
userCategories: (userCategories) => {
axios
.post(CONSTANTS.HOSTNAME + "/api/users/usercategories", userCategories)
.then((res) => {
console.log(res.data);
});
},
just use promise if function return promise:
const handleSubmit = async (e) => {
e.preventDefault();
await AuthService.addUser();
await AuthService.userCategories();
};
or make promise from function and run async
function one() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('resolve one')
return resolve("i am after five seconds")
},
2000);
});
}
function two() {
return new Promise((resolve, reject) => {
console.log('resolve two')
return resolve("i am after three seconds")
});
}
const handleSubmit = async () => {
console.log('run handleSubmit')
await one();
await two();
}
handleSubmit()

UseEffect not returning response onMount

I am running a test on page load and refresh. It is working well but the test is returning 0;
below is my code;
useEffect(() => {
setLoading(true);
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(getPosition);
} else {
setError("Your browser doesn't support geolocation");
}
const fetchLocations = async () => {
if(currentPos.latitude!==undefined && currentPos.longitude!==undefined) {
try {
const response = await instance
.get("/explore", {
params: {
ll: `${currentPos.latitude},${currentPos.longitude}`
}
})
console.log(response.data.response.groups[0].items);
setLocations(response.data.response.groups[0].items);
setError('')
setLoading(false)
} catch (error) {
setError('Error getting data');
setLoading(false)
}
}
}
fetchLocations()
}, [currentPos.latitude, currentPos.longitude]);
and my test:
What is happening here is on first mount loading... is available. On fetching data from the API is expected toHaveBeenCalledTimes to be 1 instead of returning 0.
it("renders location venues on currentlocation ", async () => {
const {getByText, container} = render(<Venues />);
getByText('Loading...')
await axiosMock.get.mockResolvedValueOnce(() =>
Promise.resolve({ data: {response } })
)
expect(axiosMock.get).toHaveBeenCalledTimes(0)
await waitForElement(() =>
container,
expect(axiosMock.get).toHaveBeenCalledTimes(1)
);
});
How can I fix this test and make it work properly?

Mock-axios-adapter not mocking get request

I'm trying to test this function:
export const fetchCountry = (query) => {
return dispatch => {
dispatch(fetchCountryPending());
return axios.get(`${process.env.REACT_APP_API_URL}/api/v1/countries/?search=${query}`)
.then(response => {
const country = response.data;
dispatch(fetchCountryFulfilled(country));
})
.catch(err => {
dispatch(fetchCountryRejected());
dispatch({type: "ADD_ERROR", error: err});
})
}
}
Here is my test:
describe('country async actions', () => {
let store;
let mock;
beforeEach(() => {
mock = new MockAdapter(axios)
store = mockStore({ country: [], fetching: false, fetched: false })
});
afterEach(() => {
mock.restore();
store.clearActions();
});
it('dispatches FETCH_COUNTRY_FULFILLED after axios request', () => {
const query = 'Aland'
mock.onGet(`/api/v1/countries/?search=${query}`).reply(200, country)
store.dispatch(countryActions.fetchCountry(query))
.then(() => {
const actions = store.getActions();
expect(actions[0]).toEqual(countryActions.fetchCountryPending())
expect(actions[1]).toEqual(countryActions.fetchCountryFulfilled(country))
});
});
When I run this test, I get an error UnhandledPromiseRejectionWarning and that fetchCountryPending was not received and that fetchCountryRejected was. It seems as if onGet() is not doing anything. When I comment out the line
mock.onGet('/api/v1/countries/?search=${query}').reply(200, country), I end up getting the exact same result, making me believe that nothing is being mocked. What am I doing wrong?
I couldn't get the .then(() => {}) to work, so I turned the function into an async function and awaited the dispatch:
it('dispatches FETCH_COUNTRY_FULFILLED after axios request', async () => {
const query = 'Aland'
mock.onGet(`/api/v1/countries/?search=${query}`).reply(200, country)
await store.dispatch(countryActions.fetchCountry(query))
const actions = store.getActions();
expect(actions[0]).toEqual(countryActions.fetchCountryPending())
expect(actions[1]).toEqual(countryActions.fetchCountryFulfilled(country))
});

Resources