How to mock and test Axios rejected promise? - javascript

I created a class called API and it's a simple wrapper around Axios
export class API {
static get = async (route: string, version: string = API_VERSION) => {
try {
return await axios.get(`${BASE_URL + version}${route}`);
} catch (error) {
throw error;
}
};
}
I'm trying to test the catch branch of the get method:
I tried:
describe('API Throws Errors', () => {
beforeEach(() => {
// axios.get.mockImplementation(() => Promise.reject('rejected'));
jest.mock('axios', () => ({
get: jest.fn().mockReturnValue(Promise.reject('error'))
}));
});
it('get fails', async () => {
await expect(() => {
API.get(GROUPS.url());
}).rejects.toEqual('error');
});
afterEach(() => {
jest.clearAllMocks();
});
});

You can mock behaviour of axios.get by using jest.mock. Put the below code above the describe section:
jest.mock('axios', () => ({
get: jest.fn().mockReturnValue(Promise.reject('error'))
}));
And you test the error like below:
it('get fails', async () => {
await expect(API.get("bad_url")).rejects.toEqual('error');
});
Exact Code
jest.mock('axios', () => ({
get: jest.fn().mockReturnValue(Promise.reject('error')),
}));
describe('API Throws Errors', () => {
it('get fails', async () => {
await expect(API.get(GROUPS.url())).rejects.toEqual('error');
});
});
Note:
If you have another test case that shouldnt be failed, you can just mock it to return Promise.resolve(). Or you can just simple clear the mock.
describe('API Throws Errors', () => {
it('get fails', async () => {
await expect(API.get(GROUPS.url())).rejects.toEqual('error');
});
it('should success', async () => {
Axios.get.mockReturnValue(Promise.resolve(SOME_VALUE));
await expect(API.get(GROUPS.url())).resolves.toEqual(SOME_VALUE);
});
});

toThrowError() is supposed to be asserted against a function and not result because if an error happens on function call, expect doesn't have a chance to be evaluated. It's applicable only to regular functions where an error is thrown. It's not applicable to async functions because they don't throw an error but return a result, which rejected promise.
rejects.toThrowError() construction is a way how rejected promise can be asserted, an assertion needs to be provided with a promise instead of a function that returns it:
await expect(API.get("bad_url")).rejects.toThrowError();

You can mock the axios for promise rejection like this
jest.mock('axios', () => ({
post: jest.fn(() => Promise.reject(new Error(''))),
get: jest.fn(() => Promise.reject(new Error(''))),
put: jest.fn(() => Promise.reject(new Error(''))),
delete: jest.fn(() => Promise.reject(new Error(''))),
}));
This method worked perfectly for me.
Hope this works for you also.

Related

Can't mock function multiple times react, testing

I want to test my component:
const Courses: React.FC = () => {
const { data, error } = useSWR(
'some url...',
fetcher
);
console.log(data, error);
if (error) {
return (
<CoursesContainer>
<Error>Something went wrong.</Error>
</CoursesContainer>
);
}
if (!data) return <Loader title="loader" />;
return (
<CoursesContainer>
<CollapsibleTable courses={data} />
</CoursesContainer>
);
};
export default Courses;
but I don't know why I can't mock it to return different value for each test. I've tried that:
jest.mock('../../utils/fetcher', () => ({
fetcher: jest
.fn()
.mockReturnValue('default')
.mockReturnValueOnce('first call')
.mockReturnValueOnce('second call'),
readData: jest.fn(),
}));
test('Basic render. fetch pending', async () => {
const component = render(<Courses />);
await waitFor(() => component.findByTitle('loader'));
expect(component.baseElement).toMatchSnapshot();
});
test('Basic render, fetch success', async () => {
const component = render(<Courses />);
await waitFor(() => component.findByText('CollapsibleTable'));
expect(component.baseElement).toMatchSnapshot();
});
test('Basic render, fetch error', async () => {
const component = render(<Courses />);
await waitFor(() => component.findByText('Something went wrong.'));
expect(component.baseElement).toMatchSnapshot();
});
and that doesn't work well. For each of tests there is only first call console.log() - The console.log(data, error); from Courses.tsx.
The feedback from jest:
console.log
undefined undefined
at Courses (src/components/Courses.tsx:14:11)
console.log
first call undefined
at Courses (src/components/Courses.tsx:14:11)
console.log
first call undefined
at Courses (src/components/Courses.tsx:14:11)
console.log
first call undefined
at Courses (src/components/Courses.tsx:14:11)
And of course the third test (Basic render, fetch error) is failed cos of that.
I can't use spyOn() instead, cos of my fetcher is separate function whithout object.
## UPDATE ##
There are my fetcher and readData functions:
const fetcher = (url: string) => {
return fetch(url)
.then((response) => response.json())
.then((data: Array<IFetchData>) => readData(data));
};
const readData = (data: Array<IFetchData>) => {
let myData: Array<ICourse> = [];
[ there are some simple operations which create new myData array with
properties which I need (there is not any async operations)]
return myData;
};
You have to give mock implementation for readData as well.
According to jest specification,
We can create a mock function with jest.fn(). If no implementation is given, the mock function will return undefined when invoked.
This will make more sense about your test.
await waitForElementToBeRemoved(() => component.getByTitle('loader'));
We're waiting for the loader title to be removed which ensures that the title shows up in the first place and now it is removed when loader is completed.
jest.mock('../../utils/fetcher', () => ({
fetcher: jest
.fn()
.mockResolvedValue('default')
.mockResolvedValueOnce('first call')
.mockResolvedValueOnce('second call'),
readData: jest.fn().mockResolvedValue('Read call'), //provide reseolve value
//jest.fn() returns undefined when we dont't provide implementation
}));
test('Basic render. fetch pending', async () => {
const component = render(<Courses />);
await waitForElementToBeRemoved(() => component.getByTitle('loader'));
expect(component.baseElement).toMatchSnapshot();
});
test('Basic render, fetch success', async () => {
const component = render(<Courses />);
await waitForElementToBeRemoved(() => component.getByText('CollapsibleTable'));
expect(component.baseElement).toMatchSnapshot();
});
test('Basic render, fetch error', async () => {
const component = render(<Courses />);
await waitForElementToBeRemoved(() => component.getByText('Something went wrong.'));
expect(component.baseElement).toMatchSnapshot();
});
#Updated answer
Sorry to say that you can't achieve what you want. The reason is the render function is called only once in your test case so it means that the fetcher and readData API will call only once.
const mockFn = jest.fn();
jest.mock('../../utils/fetcher', () => ({
fetcher: mockFn.mockResolvedValueOnce('first call'),
readData: mockFn.mockResolvedValue(['Read call']), // returns array
}));
test('Basic render. fetch pending', async () => {
const component = render(<Courses />);
await waitForElementToBeRemoved(() => component.getByTitle('loader'));
expect(mockFn).toHaveBeenCalledTimes(1); // test passed
expect(component.baseElement).toMatchSnapshot();
});
Even your provide mockResolvedValueOnce again it will give undefined as render function doesn't get a chance to call the second time to mock version of fetcher and readData.
Looks like your MockReturnValue chain is out of order. The default should be last, like this:
jest.mock('../../utils/fetcher', () => ({
fetcher: jest
.fn()
.mockReturnValueOnce('first call')
.mockReturnValueOnce('second call')
.mockReturnValue('default'),
readData: jest.fn(),
}));
See the approved answer from here:
Jest mock the same function twice with different arguments

How to validate thrown javascript exception using chai and mocha?

I have MongoDB Query function which in which query params are validated
Here is the function
Note: user is mongoose model
function fetchData(uName)
{
try{
if(isParamValid(uName))
{
return user.find({"uName":uName}).exec()
}
else {
throw "Invalid params"
}
}
catch(e)
{
throw e
}
}
To test this with invalid username values, I have written test code for that using mocha, chai, and chai-as-promised for promise-based functions
describe('Test function with invalid values', async ()=>{
it('should catch exception', async () => {
await expect(fetchData(inValidUserName)).to.throw()
})
it('should catch exception', async () => {
await expect(fetchData(inValidUserName)).to.throw(Error)
})
it('should catch exception', async () => {
await expect(fetchData(inValidUserName)).to.be.rejectedWith(Error)
})
it('should catch exception', async () => {
await expect(fetchData(inValidUserName)).to.be.rejected
})
})
None of them pass the test, How do I write a test case to handle exception for invalid userName values
You are passing the result of fetchData function call to expect function. Instead of calling the fetchData function inside expect function, pass a function to expect function.
it('should catch exception', async () => {
await expect(() => fetchData(inValidUserName)).to.throw('Invalid params')
})
Use try/catch
it('should catch exception', async () => {
try {
await fetchData(inValidUserName);
} catch(error) {
expect(error).to.exist;
}
})

Mock Axios to test .catch()

I have been trying to write tests to test a axios call but now need to test the catch part.
I have been able to do the then by mocking axios like so but can't seem to get a way to test catch. I have followed many different examples from stack overflow and the web.
jest.mock('axios', () => jest.fn(() => Promise.resolve({ data: mockData })));
but that will always return a good result so can't test the catch. The bit of code I want to test is: goToUrl() is just a window.location.assign(url) but imported.
fetchBundlesFromApi(params)
.then(({ data: { bundles } }) => {
updateBundles(bundles);
this.setState({ showUpdatingPrices: false });
})
.catch(() => goToUrl(bundlesUrl));
In my test for .then() part I do this:
const fetchedBundles = await fetchBundlesFromApi(
'?params',
);
expect(fetchedBundles.data).toEqual(mockData);
However if I follow examples like this one Mocking Axios with Jest in React - mock function not being called I can't manually mock get if I put a mock axios file in a folder __mocks__ then a lot of the test suit fails so I just want to mock it in this one test file.
here is one of the examples I tried doing:
jest.mock('axios', () => ({
get: () => jest.fn(() => Promise.resolve({ data: mockData })),
default: () => jest.fn(() => Promise.resolve({ data: mockData })),
}));
but the tests error with TypeError: (0 , _axios.default) is not a function
EDIT:
Here is my fetchBundlesApi function:
const fetchBundlesFromApi = params => axios(`${bundleRoute}/bundles${params}`);
EDIT: catch test
it('should redirect if api fails', async () => {
const networkError = new Error('Some network error');
axios.mockRejectedValueOnce(networkError);
const goToUrl = jest.fn();
let error;
try {
await fetchBundlesFromApi('?params');
} catch (err) {
error = err;
}
expect(error).toEqual(networkError);
expect(goToUrl).toHaveBeenCalled();
});
in my component I import goToUrl like so:
import { goToUrl } from 'Helpers';
You can make use of Jests ability to pop implementations off once they've run i.e. mockImplementationOnce and friends.
import axios from 'axios';
jest.mock('axios');
// default implementation
axios.get.mockResolvedValue(mockedData);
describe('#fetchBundlesFromApi', () => {
it('returns data from API', async () => {
const fetchedBundles = await fetchBundlesFromApi('?params');
expect(fetchedBundles.data).toEqual(mockData);
});
it('redirects on failure', () => {
// override behaviour for this one call
axios.get.mockRejectedValueOnce();
// verify your failure test
});
});

Jest mocks and error handling - Jest test skips the "catch" of my function

I'm creating a jest test to test if metrics were logged for the error handling of the superFetch function. My approach is creating a mock function for retryFetch and returning a Promise reject event. I expect that to go to the superFetch catch but it keeps ending up in superFetch then. What can I do to handle my errors in superFetch catch?
These are the functions:
// file: fetches.js
export function retryFetch(url) {
return new Promise((resolve, reject) => {
fetch(url).then(response => {
if (response.ok) {
resolve(response);
return;
}
throw new Error();
}).catch(error => {
createSomething(error).then(createSomething => {
reject(createSomething);
});
return;
});
});
});
export function superFetch(url, name, page) {
return retryFetch(url)
.then(response => {
return response;
}).catch(error => {
Metrics.logErrorMetric(name, page);
throw error;
});
}
My jest test:
import * as fetch from '../../src/utils/fetches';
describe('Fetch fails', () => {
beforeEach(() => {
fetch.retryFetch = jest.fn(() => Promise.reject(new Error('Error')));
});
it('error metric is logged', () => {
return fetch.superFetch('url', 'metric', 'page').then(data => {
expect(data).toEqual(null);
// received data is {"ok": true};
// why is it even going here? im expecting it to go skip this and go to catch
}).catch(error => {
// this is completely skipped. but I'm expecting this to catch an error
// received error is null, metric was not called
expect(Metrics.logErrorMetric).toHaveBeenCalled();
expect(error).toEqual('Error');
});
});
});
The problem is that you overwrite the function in the exported module but superFetch use the original one inside of the module, so the overwrite will have no effect.
You could mock fetch directly like this:
global.fetch = jest.mock(()=> Promise.reject())

Catch on JS Promise being called but not calling other jest mocked object

I have a simple js function that tries to connect to mongodb via a Promise and either succeeds of fails.
const MongodbService = () => {
mongoose.connect(mongodbUrl, options).then(() => {
logger.info('Mongodb Connected', { url: process.env.MONGODB_URL })
}).catch(err => {
logger.error('Mongodb: ${err}', { url: process.env.MONGODB_URL })
})
return mongoose
}
and I want to simply test it. I mock the logger:
import logger from '../../../config/winston'
jest.mock('../../../config/winston')
I have spec for testing success, which works as expected:
it('it should handle a connection success', async () => {
mongoose.connect = jest.fn(() => Promise.resolve())
await MongodbService()
expect(logger.info.mock.calls[0][0]).toEqual('Mongodb Connected')
expect(logger.info.mock.calls[0][1]).toEqual({ url: 'mongodb://mongodb/jb-dev' })
})
I have another which check for failure, and this is where I'm stuck:
it('it should handle a connection failure', async () => {
mongoose.connect = jest.fn(() => Promise.reject(new Error('boom')))
await MongodbService()
expect(logger.error.mock.calls[0][0]).toEqual('Mongodb Error: boom')
})
This is failing stating the logger has not been called, even though when I put a console.log in the function I can see it has correctly been called. If I add a setTimeout around the expect I can then see the logger being called, but it's been called twice. I think I'm missing something simple. Can anyone point me in the right direction?
Doh! So I was miss-understanding the information returned when I thought it was not working. Adding nextTick worked for me just fine:
it('it should handle a connection failure', async () => {
mongoose.connect = jest.fn(() => Promise.reject(new Error('bang')))
await MongodbService()
process.nextTick(() => {
expect(logger.error.mock.calls[0][0]).toEqual('Mongodb: Error: bang')
})
})

Categories

Resources