How can i test downloading excel file with jest? - reactjs

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

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 to mock a fetch call that is within an arrow function?

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();
});

Rematch/Effects- Error running test cases written with react testing Library

I am trying to write unit test cases for my models using react testing library but i am facing some issues executing the test cases.
My effects.js
export async function getStoredData(param1, store, param2) {
try {
dispatch(setLoading(true, 'getStoredData'));
// Check if key exists in store
const inputKeyCode = getInputKeyCode([param1, param2]);
let response = getUserDataState(store)[inputKeyCode];
if (!response) {
response = await getUserApi(param1, param2);
this.setUserData({ keyCode: inputKeyCode, keyValue: response });
}
return response;
} catch (error) {
// dispatch error
} finally {
dispatch(setLoading(false, 'getStoredData'));
}
}
My reducers.js
const INITIAL_STATE = {
userData: {},
};
const setUserData = (state, { key, value }) => ({ // {key: value}
...state,
userData: {
...state.userData,
[key]: value,
},
});
effects.test.js
import { getUserApi } from '../../../api/common';
jest.mock('../../../store', () => ({ dispatch: jest.fn() }));
jest.mock('../../../api/common', () => ({ getUserApi: jest.fn() }));
describe('getStoredData', () => {
const responseData = {};
setWith(responseData, 'data.userInformation', 12345);
const setUserData = jest.fn();
test('success', async () => {
getUserApi.mockResolvedValue(responseData);
await testModel.effects().getStoredData.call({ setuserData });
expect(setuserData).toHaveBeenCalled();
expect(setuserData).toHaveBeenCalledWith(12345);
});
test('failure', async () => {
getUserApi.mockRejectedValue(errorMsg);
await testModel.effects().getStoredData.call({ setuserData });
expect(showNotification).toHaveBeenCalled();
expect(showNotification).toHaveBeenCalledWith('error');
});
});
This gives me below error-
Expected mock function to have been called, but it was not called.
At line- expect(setuserData).toHaveBeenCalled();
Can someone help me understand what i am doing wrong? I guess i am doing some mistake in calling the setuserData. Any help is much appreciated.

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))
});

How to unit test Promise catch() method behavior with async/await in Jest?

Say I have this simple React component:
class Greeting extends React.Component {
constructor() {
fetch("https://api.domain.com/getName")
.then((response) => {
return response.text();
})
.then((name) => {
this.setState({
name: name
});
})
.catch(() => {
this.setState({
name: "<unknown>"
});
});
}
render() {
return <h1>Hello, {this.state.name}</h1>;
}
}
Given the answers below and bit more of research on the subject, I've come up with this final solution to test the resolve() case:
test.only("greeting name is 'John Doe'", async () => {
const fetchPromise = Promise.resolve({
text: () => Promise.resolve("John Doe")
});
global.fetch = () => fetchPromise;
const app = await shallow(<Application />);
expect(app.state("name")).toEqual("John Doe");
});
Which is working fine. My problem is now testing the catch() case. The following didn't work as I expected it to work:
test.only("greeting name is 'John Doe'", async () => {
const fetchPromise = Promise.reject(undefined);
global.fetch = () => fetchPromise;
const app = await shallow(<Application />);
expect(app.state("name")).toEqual("<unknown>");
});
The assertion fails, name is empty:
expect(received).toEqual(expected)
Expected value to equal:
"<unknown>"
Received:
""
at tests/components/Application.spec.tsx:51:53
at process._tickCallback (internal/process/next_tick.js:103:7)
What am I missing?
The line
const app = await shallow(<Application />);
is not correct in both tests. This would imply that shallow is returning a promise, which it does not. Thus, you are not really waiting for the promise chain in your constructor to resolve as you desire. First, move the fetch request to componentDidMount, where the React docs recommend triggering network requests, like so:
import React from 'react'
class Greeting extends React.Component {
constructor() {
super()
this.state = {
name: '',
}
}
componentDidMount() {
return fetch('https://api.domain.com/getName')
.then((response) => {
return response.text()
})
.then((name) => {
this.setState({
name,
})
})
.catch(() => {
this.setState({
name: '<unknown>',
})
})
}
render() {
return <h1>Hello, {this.state.name}</h1>
}
}
export default Greeting
Now we can test it by calling componentDidMount directly. Since ComponentDidMount is returning the promise, await will wait for the promise chain to resolve.
import Greeting from '../greeting'
import React from 'react'
import { shallow } from 'enzyme'
test("greeting name is 'John Doe'", async () => {
const fetchPromise = Promise.resolve({
text: () => Promise.resolve('John Doe'),
})
global.fetch = () => fetchPromise
const app = shallow(<Greeting />)
await app.instance().componentDidMount()
expect(app.state('name')).toEqual('John Doe')
})
test("greeting name is '<unknown>'", async () => {
const fetchPromise = Promise.reject(undefined)
global.fetch = () => fetchPromise
const app = shallow(<Greeting />)
await app.instance().componentDidMount()
expect(app.state('name')).toEqual('<unknown>')
})
By the looks of this snippet
.then((response) => {
return response.text();
})
.then((name) => {
this.setState({
name: name
});
})
it seems that text would return a string, which then would appear as the name argument on the next 'then' block. Or does it return a promise itself?
Have you looked into jest's spyOn feature? That would help you to mock not only the fetch part but also assert that the setState method was called the correct amount of times and with the expected values.
Finally, I think React discourages making side effects inside constructor. The constructor should be used to set initial state and other variables perhaps. componentWillMount should be the way to go :)
Recently, I have faced the same issue and ended up resolving it by following way
(taking your code as an example)
test.only("greeting name is 'John Doe'", async () => {
const fetchPromise = Promise.resolve(undefined);
jest.spyOn(global, 'fetch').mockRejectedValueOnce(fetchPromise)
const app = await shallow(<Application />);
await fetchPromise;
expect(app.state("name")).toEqual("<unknown>");});
Another way if you don't want to call done then return the next promise state to jest. Based on result of assertion( expect ) test case will fail or pass.
e.g
describe("Greeting", () => {
test("greeting name is unknown", () => {
global.fetch = () => {
return new Promise((resolve, reject) => {
process.nextTick(() => reject());
});
};
let app = shallow(<Application />);
return global.fetch.catch(() => {
console.log(app.state());
expect(app.state('name')).toBe('<unknown>');
})
});
});

Resources