Jest Axios test is passing when it shouldn't - javascript

I am writing a script (see below) to make sure that an axios function throws an error when it receives a certain status code. I found out, however, that even when I make this test fail, Jest still says that the test passes, even though it returns an error in the console (see below). Why is Jest saying this test has passed when it actually failed? Does it have something to do with me trying to expect an error, so even if the test fails, jest still receives an error (that the test failed) and thinks this means that I got what I expected? Thanks.
foo.test.js:
import axios from 'axios';
jest.mock('axios', () => ({
get: jest.fn(() => Promise.resolve({ data: 'payload' })),
}));
const getData = async (url) => {
const response = await axios.get(url);
if (response.status !== 200) {
return response.text().then((error) => {
throw new Error(error.message);
});
} else {
return response.data;
}
};
test('testing that an error is thrown', async () => {
axios.get.mockImplementation(() =>
Promise.resolve({
data: {data: 'payload'},
status: 400,
text: () => Promise.resolve(JSON.stringify({message: 'This is an error.'})),
})
);
const expectedError = async () => {
await getData('sampleUrl');
};
// The error should return 'This is an error.' and instead
// is expecting 'foo', so this test should fail.
expect(expectedError()).rejects.toThrowError('foo');
});

You need two changes to get the test to fail as expected.
Don't stringify the resolved value from text
await the expect that uses rejects
Here is an updated version that fails as expected:
import axios from 'axios';
jest.mock('axios', () => ({
get: jest.fn(() => Promise.resolve({ data: 'payload' })),
}));
const getData = async (url) => {
const response = await axios.get(url);
if (response.status !== 200) {
return response.text().then((error) => {
throw new Error(error.message);
});
} else {
return response.data;
}
};
test('testing that an error is thrown', async () => {
axios.get.mockImplementation(() =>
Promise.resolve({
data: {data: 'payload'},
status: 400,
text: () => Promise.resolve({message: 'This is an error.'}), // <= don't stringify
})
);
const expectedError = async () => {
await getData('sampleUrl');
};
await expect(expectedError()).rejects.toThrowError('foo'); // <= await
});

Related

await act( async () ...) call throws error which says to use await with act

The following hook should be tested.
export function useSomethingSaver() {
let somethingMutation = useMutation(async (newSomething) => {
return await saveSomething(newSomething)
})
return {
saveSomething: somethingMutation.mutate,
saveSomethingAsync: somethingMutation.mutateAsync,
isLoading: somethingMutation.isLoading,
isError: somethingMutation.isError,
}
}
Now I want to test if the saveSomething method is called with the right parameters when calling saveSomethingAsync.
import { act, renderHook, waitFor } from '#testing-library/react-native'
...
it('Something can be saved', async () => {
saveSomething.mockImplementation(() => SOMETHING_DATA)
const wrapper = ({ children }) => (
<BaseContexts queryClient={queryClient}>{children}</BaseContexts>
)
const { result } = renderHook(() => useSomethingSaver(), { wrapper })
await act(async () => {
await result.current.saveSomething(SOMETHING_SAVE_DATA)
})
await waitFor(() => !result.current.isLoading)
expect(saveSomething).toBeCalledWith(SOMETHING_SAVE_DATA)
})
The test is green but it puts out the following error message:
console.error
Warning: You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);
at printWarning (node_modules/react/cjs/react.development.js:209:30)
at error (node_modules/react/cjs/react.development.js:183:7)
at node_modules/react/cjs/react.development.js:2559:15
at tryCallOne (node_modules/promise/lib/core.js:37:12)
at node_modules/promise/lib/core.js:123:15
at flush (node_modules/asap/raw.js:50:29)
The problem is that the act() call is clearly awaited. Can someone please explain to me, why this error message is shown?
EDIT: act-statement with semicolons:
const { result } = renderUseSomethingSaver();
await act(async () => {
await result.current.saveSomethingAsync(SOMETHING_SAVE_DATA);
});
await waitFor(() => !result.current.isLoading);

Jasmine: Testing that a method passed in as an argument to another method gets run

My code is as follows
//agency_controller.js
import axios from 'axios';
export const getProducerNamesAndBillingPlan = ({ agencyId = '', onSuccess= (x) => x } = {} ) => {
if(!!agencyId) {
axios.get('/agency/' + agencyId)
.then(response => onSuccess.call(this, response['data']))
.catch(error => console.error(error))
}
}
//agency_controller.spec.js
import { getProducerNamesAndBillingPlan } from "../../../../app/javascript/packs/controllers/agencies_controller";
import axios from 'axios';
const mockAxiosPromise = (response) => {
return new Promise((resolve, _reject) => {
resolve({ status: 200, data: response});
});
}
describe('#getProducerNamesAndBillingPlan', () => {
...
it('calls the given onSuccess method if the request is successful', () => {
spyOn(axios, 'get').and.callFake(() => {
return mockAxiosPromise('foo')
})
const mockMethod = (x) => console.log(x)
spyOn(console.log, 'call')
getProducerNamesAndBillingPlan({ agencyId: 1, onSuccess: mockMethod })
expect(console.log.call).toHaveBeenCalledWith('foo')
})
})
I can tell that the code is working because when I run the test, 'foo' gets logged to the console. However the test still fails:
#getProducerNamesAndBillingPlan calls the given onSucess method if the request is sucessful FAILED
Expected spy call to have been called with [ 'foo' ] but it was never called.
at UserContext.<anonymous> (spec/javascripts/packs/controllers/agencies_controller.spec.js:1:17348)
Same happens with expect(console.log).toHaveBeenCalledWith('foo'). Am I doing something wrong?
The axios.get() method returns a promise, but the getProducerNamesAndBillingPlan function does not return it. You call it in the test case. When the code executes the expect statement, the promise is not resolved or rejected, so your onSuccess method was not called before the assertion.
Use async/await in test case to make sure the promise is resolved or rejected before the assertion.
agency_controller.js:
import axios from 'axios';
export const getProducerNamesAndBillingPlan = ({ agencyId = '', onSuccess = (x) => x } = {}) => {
if (!!agencyId) {
return axios
.get('/agency/' + agencyId)
.then((response) => onSuccess.call(this, response['data']))
.catch((error) => console.error(error));
}
};
agency_controller.spec.js:
import axios from 'axios';
import { getProducerNamesAndBillingPlan } from './agency_controller';
describe('#getProducerNamesAndBillingPlan', () => {
it('calls the given onSuccess method if the request is successful', async () => {
spyOn(axios, 'get').and.resolveTo({ status: 200, data: 'foo' });
const mockMethod = (x) => console.log(x);
spyOn(console, 'log');
await getProducerNamesAndBillingPlan({ agencyId: 1, onSuccess: mockMethod });
expect(console.log).toHaveBeenCalledWith('foo');
});
});
Test result:
Executing 1 defined specs...
Running in random order... (seed: 00239)
Test Suites & Specs:
1. #getProducerNamesAndBillingPlan
✔ calls the given onSuccess method if the request is successful (5ms)
>> Done!
Summary:
👊 Passed
Suites: 1 of 1
Specs: 1 of 1
Expects: 1 (0 failures)
Finished in 0.01 seconds

Jest - Test catch block in an asyncThunk (Redux toolkit)

I've been struggling recently because I'm trying to fix some tests that another developer made before he left the company I'm working on. It involves testing a catch block inside a createAsyncThunk, the thunk was created as follows:
export const onEmailSubmit = createAsyncThunk(
'email/onEmailSubmit',
async (data, { dispatch, rejectWithValue, getState }) => {
dispatch(updateEmail({ isLoadingEmailRequest: true }))
try {
const response = await updateIdentity(data)
return response.data
} catch (err) {
// If the error doesn't have any status code, there is a Network Error
// so let's show the error modal:
if (err.request) {
dispatch(updateErrorModal({ showErrorModal: true }))
}
return rejectWithValue(err.data)
}
})
The test file is something along the lines:
import userReducer, { updateEmail, handleEmailVerify } from './EmailSlice'
import configureStore from 'redux-mock-store' // ES6 modules
import thunk from 'redux-thunk'
import { updateIdentity } from '../../../http'
const middlewares = [thunk]
const mockStore = configureStore(middlewares)
jest.mock('../../../http/', () => ({
...jest.requireActual('../../../http/'),
updateIdentity: jest.fn().mockImplementation((data) => {
return Promise.resolve({
simpleFieldsValues: {
displayName: 'test',
language: 'en_US'
}
})
})
}))
describe('EmailSlice unit tests', () => {
beforeEach(() => {
jest.clearAllMocks()
Object.defineProperty(window, 'sessionStorage', {
value: {
getItem: jest.fn(() => null),
setItem: jest.fn(() => null),
removeItem: jest.fn(() => null)
},
writable: true
})
})
afterEach(() => {
jest.restoreAllMocks()
})
// ...Some tests before this one
it('tests when handleEmailVerify is called and backend does not return a response', async () => {
const error = new Error()
error.request = {
message: 'test'
}
sendVerificationCode.mockImplementation(_ => Promise.reject(error))
const store = mockStore({})
await store.dispatch(handleEmailVerify())
const actions = store.getActions()
const rejectedActionLoading = actions[1]
expect(rejectedActionLoading.type).toEqual(updateEmail.type)
expect(rejectedActionLoading.payload).toEqual({ isLoadingEmailRequest: true })
const rejectedAction = actions[2]
expect(rejectedAction.type).toEqual("errorModal/updateErrorModal")
})
})
The thing is that the tests fail because the dispatch to update the error modal is never reached. I think this is related to the fact that the mocked promise sendVerificationCode.mockImplementation(_ => Promise.reject(error)) does not return the object with the request object inside of it to the catch block, thus not dispatching the updateErrorModal.
PS: If I remove the if statement and just dispatch the updateErrorModal, the test passes.
Do you guys have any idea how to fix this?
Thanks for your time :)

How to test catch statement in async await Action

Problem
I have an Action which awaits an API function. The happy path in the try is easily testable with my mocked API. However, unsure as to the best way to test and cover the .catch.
Actions
import {getRoles} from '../shared/services/api';
export const Actions = {
SET_ROLES: 'SET_ROLES'
};
export const fetchRoles = () => async dispatch => {
try {
const response = await getRoles();
const roles = response.data;
dispatch({
type: Actions.SET_ROLES,
roles
});
} catch (error) {
dispatch({
type: Actions.SET_ROLES,
roles: []
});
}
};
Actions Test
import {fetchRoles} from '../party-actions';
import rolesJson from '../../shared/services/__mocks__/roles.json';
jest.mock('../../shared/services/api');
describe('Roles Actions', () => {
it('should set roles when getRoles() res returns', async () => {
const mockDispatch = jest.fn();
await fetchRoles()(mockDispatch);
try {
expect(mockDispatch).toHaveBeenCalledWith({
type: 'SET_ROLES',
roles: rolesJson
});
} catch (e) {
// console.log('fetchRoles error: ', e)
}
});
// Here is the problem test, how do we intentionally cause
// getRoles() inside of fetchRoles() to throw an error?
it('should return empty roles if error', async () => {
const mockDispatch = jest.fn();
await fetchRoles('throwError')(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: 'SET_ROLES',
roles: []
});
});
});
Mocked API
import rolesJson from './roles.json';
export const getRoles = async test => {
let mockGetRoles;
if (test === 'throwError') {
// console.log('sad')
mockGetRoles = () => {
return Promise.reject({
roles: []
});
};
} else {
// console.log('happy')
mockGetRoles = () => {
return Promise.resolve({
roles: rolesJson
});
};
}
try {
const roles = mockGetRoles();
// console.log('api mocks roles', roles);
return roles;
} catch (err) {
return 'the error';
}
};
^ Above you can see what I tried, which did work, but it required me to change my code in a way that fit the test, but not the actual logic of the app.
For instance, for this test to pass, I have to pass in a variable through the real code (see x):
export const fetchRoles = (x) => async dispatch => {
try {
const response = await getRoles(x);
const roles = response.data;
How can we force getRoles in our mock to throw an error in our sad path, .catch test?
You can mock getRoles API on per-test basis instead:
// getRoles will be just jest.fn() stub
import {getRoles} from '../../shared/services/api';
import rolesJson from '../../shared/services/__mocks__/roles.json';
// without __mocks__/api.js it will mock each exported function as jest.fn();
jest.mock('../../shared/services/api');
it('sets something if loaded successfully', async ()=> {
getRoles.mockReturnValue(Promise.resolve(rolesJson));
dispatch(fetchRoles());
await Promise.resolve(); // so mocked API Promise could resolve
expect(someSelector(store)).toEqual(...);
});
it('sets something else on error', async () => {
getRoles.mockReturnValue(Promise.reject(someErrorObject));
dispatch(fetchRoles());
await Promise.resolve();
expect(someSelector(store)).toEqual(someErrornessState);
})
I also propose you concentrate on store state after a call not a list of actions dispatched. Why? Because actually we don't care what actions in what order has been dispatched while we get store with data expected, right?
But sure, you still could assert against dispatch calls. The main point: don't mock result returned in __mocks__ automocks but do that on peer-basis.
I resolved the test and got the line coverage for the .catch by adding a function called mockGetRolesError in the mock api file:
Thanks to #skyboyer for the idea to have a method on the mocked file.
import {getRoles} from '../shared/services/api';
export const Actions = {
SET_ROLES: 'SET_ROLES'
};
export const fetchRoles = () => async dispatch => {
try {
const response = await getRoles();
const roles = response.data;
// console.log('ACTION roles:', roles);
dispatch({
type: Actions.SET_ROLES,
roles
});
} catch (error) {
dispatch({
type: Actions.SET_ROLES,
roles: []
});
}
};
Now in the test for the sad path, I just have to call mockGetRolesError to set the internal state of the mocked api to be in a return error mode.
import {fetchRoles} from '../party-actions';
import rolesJson from '../../shared/services/__mocks__/roles.json';
import {mockGetRolesError} from '../../shared/services/api';
jest.mock('../../shared/services/api');
describe('Roles Actions', () => {
it('should set roles when getRoles() res returns', async () => {
const mockDispatch = jest.fn();
try {
await fetchRoles()(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: 'SET_ROLES',
roles: rolesJson
});
} catch (e) {
return e;
}
});
it('should return empty roles if error', async () => {
const mockDispatch = jest.fn();
mockGetRolesError();
await fetchRoles()(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: 'SET_ROLES',
roles: []
});
});
});

Jest/enzyme: How to test for .then and .catch of an nested asynchronous function

I'm trying to do some enzyme/jest unit testing for a asynchronous function in my reactJS component, which gets injected as prop.
My Problem is to test for a value in the then() part of the function and to test for catch() if an error occures.
This is how the function of my component (<CreateAccount />) looks like:
_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) {
Cookies.set('auth-token', token, { expires: 1 })
}
}).catch(error => {
console.warn(error)
})
}
The first test should check for .catch(error => {}) as data is undefined:
it('_onSubmit() should throw error if data is missing', () => {
const createUserMutation = () => {
return Promise.resolve({})
}
const wrapper = shallow(<CreateAccount createUserMutation={createUserMutation} />)
wrapper.update().find(Form).simulate('submit', {
preventDefault: () => {}
})
const state = wrapper.instance().state
expect(wrapper).toThrow() // <-- How to do it correctly?
})
And the second test should check if cookie is set correctly. Here I don't know how to do that? I think I have to mock Cookie
it('_onSubmit() should get token', () => {
const createUserMutation = () => {
return Promise.resolve({
data: {
createUser: { token: 'token' }
}
})
}
const wrapper = shallow(<CreateAccount createUserMutation={createUserMutation} />)
wrapper.find(Form).simulate('submit', {
preventDefault: () => {}
})
// How to check for token value and Cookies?
})
What I usually have to do when I want to see if the spy worked on the catch or then, is to add another then() on the test. For example:
it('_onSubmit() should throw error if data is missing', () => {
const createUserMutation = jest.fn(() => Promise.reject(new Error()));
const spy = jest.spyOn(console,"warn");
const wrapper = shallow(<CreateAccount createUserMutation={createUserMutation} />)
wrapper.update().find(Form).simulate('submit', {
preventDefault: () => {}
});
return createUserMutation.catch(() => {
expect(wrapper).toMatchSnapshot();
})
.then(() => {
expect(spy).toHaveBeenCalledTimes(1);
});
})
I guess it is somehow related to how NodeJS handles the queues, promises, ticks, etc, internally.
That is the rejected/catch branch. If you want to test the IF path, just use a Promise.resolve and return a promise.then() instead of catch.
Why are you using console.warn for an error? Use console.error instead. You will need to mock it out to a spy as well to test it.
First test:
it('_onSubmit() should throw error if data is missing', (done) => {
const createUserMutation = () => new Promise();
const wrapper = shallow(<CreateAccount createUserMutation={createUserMutation} />)
wrapper.update().find(Form).simulate('submit', {
preventDefault: () => {}
})
const state = wrapper.instance().state
createUserMutation.resolve().then(() => {
expect(console.warn).toHaveBeenCalled();
done();
});
})
If you are running this in a mock browser environment and not a real browser then you must mock out Cookies.set.
Second test:
it('_onSubmit() should get token', (done) => {
const createUserMutation = () => new Promise();
const wrapper = shallow(<CreateAccount createUserMutation={createUserMutation} />)
wrapper.find(Form).simulate('submit', {
preventDefault: () => {}
});
jest.spyOn(window.Cookies, 'set');
const response = {
data: {
createUser: { token: 'token' }
}
}
createUserMutation.resolve(response).then(() => {
expect(window.Cookies.set).toHaveBeenCalled();
done();
});
})
afterEach(() => {
// Reset the spies so that they don't leak to other tests
jest.restoreAllMocks();
});

Categories