I would like to spy a function to test if this function is called in a catch block when a promise is rejected. My code is a react component like this
export class ResetPassword extends Component {
handleSubmit = e => {
e.preventDefault();
this.props
.resetPassword()
.then(() => {
this.props.history.push(LOGIN_PATH);
})
.catch(({ error }) => {
this.props.displayErrorAlert('impossible to change password. You should ask for a new reset password link',6000);
});
};
}
Here I want to test if the function displayErrorAlert has been called. I made this test
it('validate form', () => {
const resetPassword = () => {
return Promise.reject({
error: {
response: {
data: {
errors: [
{
title: 'foo',
},
],
},
},
},
});
};
const displaySpy = sinon.spy();
const wrapper = mount(
<ResetPassword
history={{}}
resetPassword={resetPassword}
displayErrorAlert={displaySpy}
/>
);
wrapper.instance().handleSubmit({
preventDefault: () => {},
});
expect(displaySpy.calledOnce).toEqual(true);
});
The spy is called but asynchronously of course so my test always fails. I would like to find a way to test if the function has been called only once the catch block has been called and I have no idea how to do that.
Sinon provides you everything you need when handling promises, you can resolve and reject a stubbed promise using sinon.stub().
const resetPassword = sinon.stub();
const displayErrorAlert = sinon.spy();
const preventDefault = sinon.spy();
const props = {
resetPassword,
displayErrorAlert,
history: []
};
describe('Given a component', () => {
let component;
describe('when rendered', () => {
beforeAll(() => {
component = shallow(<ResetPassword {...props} />);
});
describe('and the form is submitted and there is an error reseting the password', () => {
beforeAll(() => {
resetPassword.rejects(new Error('Oops!'));
component.find('button').simulate('click', { preventDefault });
});
it('should invoke the displayErrorAlert function', () => {
expect(displayErrorAlert.calledOnce).toBeTruthy();
});
});
});
});
I found an other solution, I return the promise in the handleSubmit function and use it in my tests.
export class ResetPassword extends Component {
handleSubmit = e => {
e.preventDefault();
return this.props
.resetPassword()
.then(() => {
this.props.history.push(LOGIN_PATH);
})
.catch(({ error }) => {
this.props.displayErrorAlert('impossible to change password. You should ask for a new reset password link',6000);
});
};
}
and my test
it('validate form', () => {
const resetPassword = () => {
return Promise.reject({
error: {
response: {
data: {
errors: [
{
title: 'foo',
},
],
},
},
},
});
};
const displaySpy = sinon.spy();
const wrapper = mount(
<ResetPassword
history={{}}
resetPassword={resetPassword}
displayErrorAlert={displaySpy}
/>
);
expect.assertions(1);
const promise = wrapper.instance().handleSubmit({
preventDefault: () => {},
});
return promise.then(() => {
expect(displaySpy.calledOnce).toEqual(true);
});
});
Related
I've got this custom hook:
import React from 'react';
import { useMessageError } from 'components/Message/UseMessage';
export interface Country {
code: string;
name: string;
}
export default function useCountry(): Array<Country> {
const [countries, setCountries] = React.useState<Country[]>([]);
const { showErrorMessage } = useMessageError();
React.useEffect(() => {
fetch('/api/countries', {
method: 'GET',
})
.then(data => data.json())
.then(function(data) {
// ..
})
.catch(() => showErrorMessage());
}, []);
return countries;
}
I want to test catching an error if there will be invalid response. With that, error message should appear thanks to showErrorMessage(). And I've got this test:
const showErrorMessage = jest.fn();
jest.mock('components/Message/UseMessage', () => ({
useMessageError: () => ({
showErrorMessage: showErrorMessage,
}),
}));
import useCountry from 'components/Country/useCountry';
import { renderHook } from '#testing-library/react-hooks';
import { enableFetchMocks } from 'jest-fetch-mock';
enableFetchMocks();
describe('The useCountry hook', () => {
it('should show error message', async () => {
jest.spyOn(global, 'fetch').mockImplementation(() =>
Promise.resolve({
json: () => Promise.reject(),
} as Response),
);
const { result, waitForNextUpdate } = renderHook(() => useCountry());
await waitForNextUpdate();
expect(fetch).toHaveBeenCalled();
expect(showErrorMessage).toHaveBeenCalled();
expect(result.current).toEqual([]);
});
});
But with that, I'm getting an error:
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error
What I'm doing wrong in here? I assume it is somehow related with await waitForNextUpdate();, but I really don't know for sure and how to manage with it.
waitForNextUpdate() waits for next update but your hook does not trigger it since it only calls showErrorMessage(). Take a look at this sandbox
As a straightforward solution something that triggers an update can be added:
React.useEffect(() => {
fetch('/api/countries', {
method: 'GET',
})
.then(data => data.json())
.then(function(data) {
// ..
})
.catch(() => {
showErrorMessage();
// trigger update in any suitable way, for example:
setCountries([]);
});
}, []);
But it may be better to refactor it in some way. For example, you could use a separate hook and state for errors:
export default function useCountry(): Array<Country> {
const [countries, setCountries] = React.useState<Country[]>([]);
const [error, setError] = React.useState(null);
const { showErrorMessage } = useMessageError();
React.useEffect(() => {
fetch('/api/countries', {
method: 'GET',
})
.then(data => data.json())
.then(function(data) {
// ..
})
.catch(() => setError(true));
}, []);
React.useEffect(() => {
if (error) {
showErrorMessage()
}
}, [error]);
return countries;
}
I am trying to test my service which have one function saveWithoutSubmit
export const saveWithoutSubmit = async (
values,
orderId,
taskId,
fseMsisdn,
updateTaskListAfterFilter
) => {
var obj = {
remarks: values.remarks,
requestedBy: localStorage.getItem("msisdn")
};
try {
const response = await sendPostRequest(`${API_TASK_URL}closeSr`, {
...obj,
saveWithoutSubmit: true
});
if (response && response.data && response.data.status.code !== "200") {
error(response.data.result.message);
} else {
console.log(response);
success(response.data.status.message);
updateTaskListAfterFilter();
}
} catch (e) {
if (e.response && e.response.data) {
console.log(e.response.data.message);
error(e.response.data.status.message);
}
}
};
I want to check success or error method is called or not ? or updateTaskListAfterFilter is called or not?
I tried like this
https://codesandbox.io/s/ecstatic-currying-5q1b8
describe("remark service test", () => {
const fakeAxios = {
get: jest.fn(() => Promise.resolve({ data: { greeting: "hello there" } }))
};
it("save without sumit", () => {
const updateTaskListAfterFilter = () => {};
saveWithoutSubmit({}, updateTaskListAfterFilter);
expect(updateTaskListAfterFilter).toBeCalled();
});
});
can you please suggest how i will test async methods or post request (using mook data)??
so that my test cases will be passed.
I want to check if I got success from promise my success method will be called else error
any update ?..!!
update
https://codesandbox.io/s/ecstatic-currying-5q1b8
it("save without sumit", async () => {
const sendPostRequest = jest.fn(() =>
Promise.resolve({ data: { greeting: "hello there" } })
);
const updateTaskListAfterFilter = () => {};
saveWithoutSubmit({}, updateTaskListAfterFilter);
expect(updateTaskListAfterFilter).toBeCalled();
});
it("save without sumit", async () => {
const sendPostRequest = jest.fn(() =>
Promise.resolve({ data: { greeting: "hello there" } })
);
const mockUpdateTaskListAfterFilter = jest.fn();
const updateTaskListAfterFilter = () => {};
saveWithoutSubmit({}, updateTaskListAfterFilter);
expect(updateTaskListAfterFilter).toBeCalled();
await wait(() => {
expect(mockUpdateTaskListAfterFilter).toBeCalled();
});
});
You should change it("save without sumit", () => { to it("save without sumit", async () => {.
Then you usually use jest.fn() to create a mock function that you will give to another function or component.
Finally, await the mock function to be called:
await wait(() => {
expect(mockUpdateTaskListAfterFilter).toBeCalled();
});
Alternatively, you can await some other event that you know will occur before your mock is called, like some other mock getting called or something appearing on the page, and then check that your mock was called.
zoomOut(callback) {
// Zooms out the current screen
this.view.current.zoomOut(300).done(() => {
(hasCallback(callback)) && callback();
});
}
I'm trying to test the function above but I keep getting the following error:
TypeError: this.view.current.zoomOut(...).done is not a function
How can I mock this method chain in Jest?
Thanks to BudgieInWA, I was able to solve this problem by returning done.
For those who are testing a React component with Enzyme, here's how you can do it:
it('should call callback', () => {
const wrapper = shallow(<Zoom {...minProps}/>);
const instance = wrapper.instance();
const callback = jest.fn();
instance.view = {
current: {
zoomOut: jest.fn(() => {
return {
done: jest.fn((callback) => {
callback();
})
};
})
}
};
expect(callback).toHaveBeenCalledTimes(0);
instance.zoomOut(callback);
expect(callback).toHaveBeenCalledTimes(1);
});
You could try this:
const mockZoomOut = jest.fn(() => ({ done(cb) { cb(); } }));
const mockThis = {
view: {
current: {
zoomOut: mockZoomOut,
},
},
};
test('it does', () => {
const cb = jest.fn();
zoomOut.apply(mockThis, [cb]);
expect(mockZoomOut).toHaveBeenCalledTimes(1);
expect(cb).toHaveBeenCalledTimes(1);
});
See Jest Mock Functions and fn.apply.
If you are testing the behaviour of the class as a whole, then you could set up the instance that you are testing to have this.view.current.zoomOut be mockZoomOut somehow.
Below I have a test for my login actions. I'm mocking a Firebase function and want to test if the signIn/signOut functions are called.
The tests pass. However, I do not see my second console log. Which is this line console.log('store ==>', store);.
it('signIn should call firebase', () => {
const user = {
email: 'first.last#yum.com',
password: 'abd123'
};
console.log('111');
return store.dispatch(signIn(user.email, user.password)).then(() => {
console.log('222'); // Does not reach
expect(mockFirebaseService).toHaveBeenCalled();
});
console.log('333');
});
● login actions › signIn should call Firebase
TypeError: auth.signInWithEmailAndPassword is not a function
Action being tested
// Sign in action
export const signIn = (email, password, redirectUrl = ROUTEPATH_DEFAULT_PAGE) => (dispatch) => {
dispatch({ type: USER_LOGIN_PENDING });
return firebase
.then(auth => auth.signInWithEmailAndPassword(email, password))
.catch((e) => {
console.error('actions/Login/signIn', e);
// Register a new user
if (e.code === LOGIN_USER_NOT_FOUND) {
dispatch(push(ROUTEPATH_FORBIDDEN));
dispatch(toggleNotification(true, e.message, 'error'));
} else {
dispatch(displayError(true, e.message));
setTimeout(() => {
dispatch(displayError(false, ''));
}, 5000);
throw e;
}
})
.then(res => res.getIdToken())
.then((idToken) => {
if (!idToken) {
dispatch(displayError(true, 'Sorry, there was an issue with getting your token.'));
}
dispatch(onCheckAuth(email));
dispatch(push(redirectUrl));
});
};
Full Test
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
// Login Actions
import {
// onCheckAuth,
signIn,
signOut
} from 'actions';
import {
// USER_ON_LOGGED_IN,
USER_ON_LOGGED_OUT
} from 'actionTypes';
// String Constants
// import { LOGIN_USER_NOT_FOUND } from 'copy';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
// Mock all the exports in the module.
function mockFirebaseService() {
return new Promise(resolve => resolve(true));
}
// Since "services/firebase" is a dependency on this file that we are testing,
// we need to mock the child dependency.
jest.mock('services/firebase', () => new Promise(resolve => resolve(true)));
describe('login actions', () => {
let store;
beforeEach(() => {
store = mockStore({});
});
it('signIn should call firebase', () => {
const user = {
email: 'first.last#yum.com',
password: 'abd123'
};
console.log('111');
return store.dispatch(signIn(user.email, user.password)).then(() => {
console.log('222'); // does not reach
expect(mockFirebaseService).toHaveBeenCalled();
});
console.log('333');
});
it('signOut should call firebase', () => {
console.log('signOut should call firebasew');
store.dispatch(signOut()).then(() => {
expect(mockFirebaseService).toHaveBeenCalled();
console.log('store ==>', store);
expect(store.getActions()).toEqual({
type: USER_ON_LOGGED_OUT
});
});
console.log('END');
});
});
You have two issues here,
The tests pass however I do not see my 2nd console log. Which is this
line console.log('store ==>', store);.
That is because the test is not waiting for the promise to fulfill, so you should return it:
it('signOut should call firebase', () => {
console.log('signOut should call firebasew');
return store.dispatch(signOut()).then(() => { // NOTE we return the promise
expect(mockFirebaseService).toHaveBeenCalled();
console.log('store ==>', store);
expect(store.getActions()).toEqual({
type: USER_ON_LOGGED_OUT
});
console.log('END');
});
});
You can find examples in the Redux official documentation.
Secondly, your signIn test is failing because you have mocked the wrong firebase:
jest.mock('services/firebase', () => new Promise(resolve => resolve(true)));
That should probably look more like:
jest.mock('services/firebase', () => new Promise(resolve => resolve({
signInWithEmailAndPassword: () => { return { getIdToken: () => '123'; } }
})));
Login actions › signIn should call firebase
TypeError: auth.signInWithEmailAndPassword is not a function
This tells that your store.dispatch(signIn(user.email, user.password)) fails, thus your second console.log won't go into your then chain, use catch or second callback argument of then instead.
I'm trying to write a unit test (enzyme/jest) for this function:
_onSubmit = (event) => {
event.preventDefault()
const { username, password } = this.state
this.props.createUserMutation({
variables: { username, password }
}).then(response => {
const token = response.data.createUser.token
if (token) {
this.setState({ token })
}
}).catch(error => {
console.warn(error)
})
}
But I don't know how to handle this.props.createUserMutation() for proper testing.
Of course my current attempt throws a TypeError: _this.props.createUserMutation is not a function error
Unit test
it('_onSubmit() should submit form and reset state object', () => {
const wrapper = shallow(<CreateAccount />)
wrapper.setState({ username: 'Username', password: 'Password' })
wrapper.find(Form).simulate('submit', {
preventDefault: () => {}
})
const state = wrapper.instance().state
expect(state).toEqual({ token: 'test' })
})
Updated unit test
it('_onSubmit() should submit data and get result dataset', () => {
const createUserMutation = () => {
return Promise.resolve({
data: {
createUser: { token: 'token' }
}
})
}
const wrapper = shallow(<CreateAccount createUserMutation={createUserMutation} />)
wrapper.find(Form).simulate('submit', {
preventDefault: () => {}
})
const state = wrapper.instance().state
expect(state).toEqual({ token: 'token' })
})
console.log(state) doesn't give me a token.
I'm assuming createUserMutation is injected to props by the compose function.
So, what you can do is import the non-default component in your test instead of the 'connected' one.
import {CreateAccount} from './CreateAcount';
Then, you can pass createUserMutation as props directly to the component when mounting it.
it('_onSubmit() should submit form and reset state object', () => {
const createUserMutation = () => {
return Promise.resolve({
username: '',
password: ''
});
};
const wrapper = shallow(<CreateAccount createUserMutation={createUserMutation} />)
...
});
Given that createUserMutation seems to be a function that returns a promise, you can assign it Promise.resolve() in order to execute the then function which changes the state. Which is basically what you are testing.
Moreover, you're not able to test the token change in the state because the verification in the test is being run before the state changing in the method _onSubmit. (You can check this by putting console statements in both places and see which executes first).
What you need to do is return the promise in your _onSubmit method.
_onSubmit = (event) => {
event.preventDefault()
const { username, password } = this.state
return this.props.createUserMutation({ // returning the promise
variables: { username, password }
}).then(response => {
const token = response.data.createUser.token
if (token) {
this.setState({ token })
}
}).catch(error => {
console.warn(error)
})
}
Then, in your unit test you need to simulate the submit event by calling the function directly from props in order to put your verifications inside a then function. By doing this, you would run your expect statement after the state has been changed.
it('_onSubmit() should submit data and get result dataset', () => {
const createUserMutation = () => {
return Promise.resolve({
data: {
createUser: { token: 'token' }
}
})
}
const wrapper = shallow(<CreateAccount createUserMutation={createUserMutation} />)
wrapper.find(Form).props().onSubmit({
preventDefault: () => {}
}).then(() =< {
const state = wrapper.instance().state
expect(state).toEqual({ token: 'token' })
})
})