Test then() inside of a promise API call jest - javascript

I am trying to test a function which is an api call / promise so I can check state inside then using jest but cant seem to figure out what to do. I have tried mocking the file and the function to return a promise but getting an error TypeError: (0 , _dataAccess.fetchBundlesFromApi) is not a function I've tried following the docs on jests website and also the many different answers from stack overflow but none seem to work. Here is the code I want to test. I want to be able to call that and then say if okay check state or if error do something else. below is the code i am trying to do and the mocking that I have tried.
getLatestPrices = params => {
const { updateBundles } = this.props;
fetchBundlesFromApi(params)
.then(({ data: { bundles } }) => {
updateBundles(bundles);
this.setState({ showUpdatingPrices: false });
window.TemplateCalculator.reload();
})
.catch(() => goToUrl(bundlesUrl));
};`
fetchBundlesFromApi is import { fetchBundlesFromApi } from '../../../../dataAccess'; which is an axios call:
const fetchBundlesFromApi = params => axios(`${bundleRoute}/bundles${params}`);
export { fetchBundlesFromApi };
This is the mocking I have tried.
jest.mock('../../../../dataAccess', () => ({
fetchBundlesFromApi: new Promise(resolve => resolve({ data: mockBundles })),
}));
I have also tried these websites:
https://binarapps.com/blog/test-ajax-calls-in-react-component-lifecycle.
Jest/Enzyme Error: "Method 'setState' is only meant to run on a single node. 3 found instead."
https://jestjs.io/docs/en/asynchronous

I worked out I had to import my api call function like so:
import { fetchBundlesFromApi } from '../../../../dataAccess';
As that function was using axios I had to mock that. I did that like so:
jest.mock('axios', () => jest.fn(() => Promise.resolve({ data: mockBundles })));
The in my test I made it async and I could await that function response.
const fetchedBundles = await fetchBundlesFromApi(
'params',
);

Related

response undefined when using jest mock to test axios in vue

I have tried lots of answers to similar questions, but none of them helps. (the comments are what I have tried. attempts are separated by a blank line. I have stuck here for almost a week, trying to improve the coverage by testing the .then part of the Axios request. really cannot figure out which part goes wrong.
code here
__ mocks __/axios.js:
export default {
get: jest.fn(() => Promise.resolve({ data: {} })),
post: jest.fn(() => Promise.resolve())
}
getInfo method to be tested:
getInfo () {
axios.get('/')
.then((response) => {
this.form.sid = response.data.data.basic_info.username
this.form.name = response.data.data.basic_info.name
this.form.tel = response.data.data.basic_info.mobile_number
this.form.email = response.data.data.basic_info.email
return response.data
}).catch(function (error) {
console.log(error)
})
}
test code:
import { shallowMount } from '#vue/test-utils'
import InfoForm from '#/components/InfoForm'
//attempt 1
import axios from 'axios';
jest.mock('axios')
//attempt 2
// import axios from './__mocks__/axios'
// jest.mock('axios')
//attempt 3
// import axios from './__mocks__/axios'
// jest.mock("axios", () => ({
// post: jest.fn((_url, _body) => {
// url = _url
// body = _body
// return Promise.resolve()
// }),
// get: jest.fn(() => {
// return Promise.resolve()
// })
// }))
//attempt 4
// import axios from "axios"
//...
//describe part is skipped here
it('test method: getInfo', async () => {
const mockAxiosGet = jest.spyOn(axios, "get")
mockAxiosGet.mockResolvedValue(() =>
Promise.resolve({
data: {
basic_info: {
username: 'aName',
name: 'aNAME',
mobile_number: '12222222222',
email: 'amail#em.com'
}
}
})
)
const response = await wrapper.vm.getInfo()
expect(response).toBeDefined() // error(plz see error message below)
})
error message:
Ensure that a variable is not undefined.
InfoForm.vue test > test method: getInfo
-----
Error: expect(received).toBeDefined()
Received: undefined Jest
any help is highly appreciated!
getInfo is a common pitfall with promises. Promises shouldn't be encapsulated and should be returned from a function that contains them in order for them to be chained later. Even if this isn't currently needed, this may be needed later for a function to be composed or tested. The only reason to not do this is that a callback may need a specific signature that doesn't allow to return promise object.
It should be:
getInfo () {
return axios.get('/')
...

Mocking a function returned by a react hook

I'm building a pagination using the useQuery hook as part of the Apollo Client in React, which exposes a function called fetchMore seen here: https://www.apollographql.com/docs/react/data/pagination/
Everything works fine, but I'm trying to write a test one of the use cases, which is when the fetchMore function fails due to a network error. The code in my component looks like this.
const App = () => {
// Some other component logic
const {loading, data, error, fetchMore} = useQuery(QUERY)
const handleChange = () => {
fetchMore({
variables: {
offset: data.feed.length
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return Object.assign({}, prev, {
feed: [...prev.feed, ...fetchMoreResult.feed]
});
}
}).catch((e) => {
// handle the error
})
}
}
Basically I want to test the case where the fetchMore function function throws an Error. I DON'T want to mock the entire useQuery though, just the fetchMore function. What would be the best way to mock just the fetchMore function in my test?
One way to do it is to just mock the hook
In your spec file:
import { useQuery } from '#apollo/react-hooks'
jest.mock('#apollo/react-hooks',() => ({
__esModule:true
useQuery:jest.fn()
});
console.log(useQuery) // mock function - do whatever you want!
/*
e.g. useQuery.mockImplementation(() => ({
data:...
loading:...
fetchMore:jest.fn(() => throw new Error('bad'))
});
*/
You could also mock the stuff that goes on "behind the scenes" to simulate a network error, do whatever you need to to test your catch.
EDIT:
Search for __esModule: true on this page and you'll understand.
It's probably easier to just mock the whole function and return everything as mock data. But you can unmock it to use the real one so as not to conflict with other tests.

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

How can I test a class which contains imported async methods in it?

This is my first time working with tests and I get the trick to test UI components. Now I am attempting to test a class which has some static methods in it. It contains parameters too.
See the class:
import UserInfoModel from '../models/UserInfo.model';
import ApiClient from './apiClient';
import ApiNormalizer from './apiNormalizer';
import Article from '../models/Article.model';
import Notification from '../models/Notification.model';
import Content from '../models/Link.model';
export interface ResponseData {
[key: string]: any;
}
export default class ApiService {
static makeApiCall(
url: string,
normalizeCallback: (d: ResponseData) => ResponseData | null,
callback: (d: any) => any
) {
return ApiClient.get(url)
.then(res => {
callback(normalizeCallback(res.data));
})
.catch(error => {
console.error(error);
});
}
static getProfile(callback: (a: UserInfoModel) => void) {
return ApiService.makeApiCall(`profile`, ApiNormalizer.normalizeProfile, callback);
}
}
I already created a small test which is passing but I am not really sure about what I am doing.
// #ts-ignore
import moxios from 'moxios';
import axios from 'axios';
import { baseURL } from './apiClient';
import { dummyUserInfo } from './../models/UserInfo.model';
describe('apiService', () => {
let axiosInstance: any;
beforeEach(() => {
axiosInstance = axios.create();
moxios.install();
});
afterEach(() => {
moxios.uninstall();
});
it('should perform get profile call', done => {
moxios.stubRequest(`${baseURL.DEV}profile`, {
status: 200,
response: {
_user: dummyUserInfo
}
});
axiosInstance
.get(`${baseURL.DEV}profile`)
.then((res: any) => {
expect(res.status).toEqual(200);
expect(res.data._user).toEqual(dummyUserInfo);
})
.finally(done);
});
});
I am using moxios to test the axios stuff -> https://github.com/axios/moxios
So which could be the proper way to test this class with its methods?
Introduction
Unit tests are automated tests written and run by software developers to ensure that a section of an application meets its design and behaves as intended. As if we are talking about object-oriented programming, a unit is often an entire interface, such as a class, but could be an individual method.
The goal of unit testing is to isolate each part of the program and show that the individual parts are correct. So if we consider your ApiService.makeApiCall function:
static makeApiCall(
url: string,
normalizeCallback: (d: ResponseData) => ResponseData | null,
callback: (d: any) => any
) {
return ApiClient.get(url)
.then((res: any) => {
callback(normalizeCallback(res.data));
})
.catch(error => {
console.error(error);
});
}
we can see that it has one external resource calling ApiClient.get which should be mocked. It's not entirely correct to mock the HTTP requests in this case because ApiService doesn't utilize them directly and in this case your unit becomes a bit more broad than it expected to be.
Mocking
Jest framework provides great mechanism of mocking and example of Omair Nabiel is correct. However, I prefer to not only stub a function with a predefined data but additionally to check that stubbed function was called an expected number of times (so use a real nature of mocks). So the full mock example would look as follows:
/**
* Importing `ApiClient` directly in order to reference it later
*/
import ApiClient from './apiClient';
/**
* Mocking `ApiClient` with some fake data provider
*/
const mockData = {};
jest.mock('./apiClient', function () {
return {
get: jest.fn((url: string) => {
return Promise.resolve({data: mockData});
})
}
});
This allows to add additional assertions to your test example:
it('should call api client method', () => {
ApiService.makeApiCall('test url', (data) => data, (res) => res);
/**
* Checking `ApiClient.get` to be called desired number of times
* with correct arguments
*/
expect(ApiClient.get).toBeCalledTimes(1);
expect(ApiClient.get).toBeCalledWith('test url');
});
Positive testing
So, as long as we figured out what and how to mock data let's find out what we should test. Good tests should cover two situations: Positive Testing - testing the system by giving the valid data and Negative Testing - testing the system by giving the Invalid data. In my humble opinion the third branch should be added - Boundary Testing - Test which focus on the boundary or limit conditions of the software being tested. Please, refer to this Glossary if you are interested in other types of tests.
The positive test flow flow for makeApiCall method should call normalizeCallback and callback methods consequently and we can write this test as follows (however, there is more than one way to skin a cat):
it('should call callbacks consequently', (done) => {
const firstCallback = jest.fn((data: any) => {
return data;
});
const secondCallback = jest.fn((data: any) => {
return data;
});
ApiService.makeApiCall('test url', firstCallback, secondCallback)
.then(() => {
expect(firstCallback).toBeCalledTimes(1);
expect(firstCallback).toBeCalledWith(mockData);
expect(secondCallback).toBeCalledTimes(1);
expect(secondCallback).toBeCalledWith(firstCallback(mockData));
done();
});
});
Please, pay attention to several things in this test:
- I'm using done callback to let jest know the test was finished because of asynchronous nature of this test
- I'm using mockData variable which the data that ApiClient.get is mocked this so I check that callback got correct value
- mockData and similar variables should start from mock. Otherwise Jest will not allow to out it out of mock scope
Negative testing
The negative way for test looks pretty similar. ApiClient.get method should throw and error and ApiService should handle it and put into a console. Additionaly I'm checking that none of callbacks was called.
import ApiService from './api.service';
const mockError = {message: 'Smth Bad Happened'};
jest.mock('./apiClient', function () {
return {
get: jest.fn().mockImplementation((url: string) => {
console.log('error result');
return Promise.reject(mockError);
})
}
});
describe( 't1', () => {
it('should handle error', (done) => {
console.error = jest.fn();
const firstCallback = jest.fn((data: any) => {
return data;
});
const secondCallback = jest.fn((data: any) => {
return data;
});
ApiService.makeApiCall('test url', firstCallback, secondCallback)
.then(() => {
expect(firstCallback).toBeCalledTimes(0);
expect(secondCallback).toBeCalledTimes(0);
expect(console.error).toBeCalledTimes(1);
expect(console.error).toBeCalledWith(mockError);
done();
});
});
});
Boundary testing
Boundary testing could be arguing in your case but as long as (according to your types definition normalizeCallback: (d: ResponseData) => ResponseData | null) first callback can return null it could be a good practice to check if is the successfully transferred to a second callback without any errors or exceptions. We can just rewrite our second test a bit:
it('should call callbacks consequently', (done) => {
const firstCallback = jest.fn((data: any) => {
return null;
});
const secondCallback = jest.fn((data: any) => {
return data;
});
ApiService.makeApiCall('test url', firstCallback, secondCallback)
.then(() => {
expect(firstCallback).toBeCalledTimes(1);
expect(firstCallback).toBeCalledWith(mockData);
expect(secondCallback).toBeCalledTimes(1);
done();
});
});
Testing asynchronous code
Regarding testing asynchronous code you can read a comprehensive documentation here. The main idea is when you have code that runs asynchronously, Jest needs to know when the code it is testing has completed, before it can move on to another test. Jest provides three ways how you can do this:
By means of a callback
it('the data is peanut butter', done => {
function callback(data) {
expect(data).toBe('peanut butter');
done();
}
fetchData(callback);
});
Jest will wait until the done callback is called before finishing the test. If done() is never called, the test will fail, which is what you want to happen.
By means of promises
If your code uses promises, there is a simpler way to handle asynchronous tests. Just return a promise from your test, and Jest will wait for that promise to resolve. If the promise is rejected, the test will automatically fail.
async/await syntax
You can use async and await in your tests. To write an async test, just use the async keyword in front of the function passed to test.
it('the data is peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
Example
Here you can find a ready to use example of your code
https://github.com/SergeyMell/jest-experiments
Please, let me know if something left unclear for you.
UPDATE (29.08.2019)
Regarding your question
Hi, what can I do to mock ./apiClient for success and error in the same file?
According to the documentation Jest will automatically hoist jest.mock calls to the top of the module (before any imports). It seems that you can do setMock or doMock instead, however, there are issues with mocking this way that developers face from time to time. They can be overridden by using require instead of import and other hacks (see this article) however I don't like this way.
The correct way for me in this case is do split mock defining and implementation, so you state that this module will be mocked like this
jest.mock('./apiClient', function () {
return {
get: jest.fn()
}
});
But the implementation of the mocking function differs depending on scope of tests:
describe('api service success flow', () => {
beforeAll(() => {
//#ts-ignore
ApiClient.get.mockImplementation((url: string) => {
return Promise.resolve({data: mockData});
})
});
...
});
describe('api service error flow', () => {
beforeAll(() => {
//#ts-ignore
ApiClient.get.mockImplementation((url: string) => {
console.log('error result');
return Promise.reject(mockError);
})
});
...
});
This will allow you to store all the api service related flows in a single file which is what you expected as far as I understand.
I've updated my github example with api.spec.ts which implements all mentioned above. Please, take a look.
The unit test term is self-explanatory that you test a unit. A function in complete isolation. Any outside dependencies are mocked. Here if your'e testing makeApiCall function you'll have to stub it's parameters and then mock the ApiClient promise and expect the function to return whatever you're expecting it to return with respect to your mocked and stub parameters.
One thing that people normally forget and which is the most important is to test the negative cases of a function. What will happen if your function throws an error will it break the app. How your function behaves in case something fails. Tests are written to avoid breaking changes in the app.
here is a better guide how to test async functions in JEST which coding examples:
https://www.leighhalliday.com/mocking-axios-in-jest-testing-async-functions
Hope this helps
UPDATE
Mock your ApiClient
for pass case:
jest.mock('./apiClient', () => {
get: jest.fn(() => Promise.resolve(data)) // for pass case
})
for fail case:
jest.mock('./apiClient', () => {
get: jest.fn(() => Promise.reject(false)) // for fail case
})
now call your makeApiCall for both cases once for success and once for fail.
for fail case:
const makeCall = await makeApiCall( <your stub params here> )
expect(makeCall).toThrowError() // note here you can check whatever you have done to handle error. ToThrowError is not a built-in function but just for understanding
I've mostly done testing in Jasmine so this last piece of code is kind of a psuedo code.
I guess what you are asking is how to test ApiService. If this is the case, then mocking the very own thing you want to test would make the unit test pointless.
What I would expect is the following items
You just want to test logic in your own class, not in the library.
You don't want to make an actual network request, this spams the server and make the test slower to run.
If this is the case, then you should mock out some lib to control their behaviour and see how your class behave under those circumstances. And, mock out any operation that involves network IO, make your test faster and less reliant on external resources.
There are a few things you could check with some dependencies mocked out:
delegation, e.g. is axios called once, with the right param?
directly mock the behaviour of the lib, in your case using maxios.
import ApiService, { baseURL } from './apiClient';
describe('ApiService', () => {
let axiosInstance: any;
beforeEach(() => {
axiosInstance = axios.create();
moxios.install();
});
afterEach(() => {
moxios.uninstall();
});
// usually 1 test suite for each method
describe('#getProfile', (done) => {
// mocking behaviour
it('should perform get profile call', () => {
moxios.stubRequest(`${baseURL.DEV}profile`, {
status: 200,
response: {
_user: dummyUserInfo
}
});
ApiService.getProfile((profile) => {
expect(profile).toEqual(dummyUserInfo); // you get what i mean
done();
});
});
// directly mock axios
it('delegates to axios', (done) => {
// you should put this to the top to avoid confusion, it will be hoisted
jest.mock('axios', () => ({
create: jest.fn(() => ({
get: jest.fn(() => Promise.resolve()),
})),
}));
ApiService.getProfile((profile) => {
// do some assertion
expect(axiosInstance.get).toHaveBeenCalledTimes(1);
expect(axiosInstance.get).toHaveBeenCalledWith(url, someParam, youGetIt);
done();
});
});
// rmb to test some error case
it('should throw when param is not correct', (done) => { ... });
});
});

How to unit test/stub async call in redux

How is the proper way to unit test the following redux async action?
const client = contentful.createClient(clientConfig);
export const fetchNavigation = () => {
return dispatch => {
return client.getEntries({content_type: 'navigation'})
.then((entries) => {
console.log('All entries for content_type = navigation')
dispatch(receiveNavigation(entries))
})
.catch(error => {
console.log('Something went wrong');
dispatch(fetchNavigationFailure(error));
});
}
}
I don't know how to customise the web request response body performed by client.getEntries. I think that replacing the getEntries function with my own one would do the trick. However, I don't know where to start to do that.
Here is the unit test I wrote:
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
describe('fetchNavigation', () => {
it('creates RECEIVE_NAVIGATION when fetching navigation is done', () => {
// Here I should prepare the client.getEntries() returned promise
const expectedBodyResponse = { includes: ['do something', 'yay!'] }
const expectedActions = [
{ type: actions.RECEIVE_NAVIGATION, navigation: expectedBodyResponse }
]
const store = mockStore({ todos: [] })
return store.dispatch(actions.fetchNavigation())
.then(() => {
expect(store.getActions()).toEqual(expectedActions)
})
})
})
IMO mocking getEntries (and probably createClient) seems to be the right way to do. :)
It depends how you load the contentful sdk. As I see you're using ES Modules and Jasmine, right?
To mock the getEntries function you have to mock the createClient as the client is not accessible from within your test.
I think this this answer might be what you're looking for.
I just wrote down an example.
import contentful from 'contentful';
export const fetchNavigation = () => {
return (dispatch) => {
return contentful.createClient({ accessToken: 'fooo', space: 'bar' })
.getEntries({ content_type: 'navigation' })
.then(() => {
dispatch('yeah');
})
.catch(error => console.error('Something went wrong', error));
};
};
import { fetchNavigation } from '../Action';
import * as contentful from 'contentful';
describe('Contentful mocking', () => {
it('should be possible to mock Contentful', (done) => {
const client = { getEntries: () => { return Promise.resolve(); } };
const spy = {
fn: (value) => {
expect(value).toBe('yeah');
done();
},
};
spyOn(contentful.default, 'createClient').and.returnValue(client);
fetchNavigation()(spy.fn);
});
});
I had to move the createClient call into the action itself, because otherwise I don't think it's possible to reach and mock it when it's hidden in the module scope. I then used the import * as contentful from 'contentful' to mock and overwrite the needed functionality and to have the flexibility to adjust everything to my needs.
The usage of the createClient feels a bit unfortunate for me. I'd probably restructure everything a bit and would pass the client as dependency of all the actions? This way the mocking would become way easier and when you also have several action modules, there is most probably no need to initialize the client several times?
I solved in this way.
First I moved the creation of the client to its own file with functions initClient() and getClient(). The module is called contentfulClient.
Then, I found out that it is possible to mock the function of an instantiated object in sinon:
import * as contentful from './services/contentfulClient';
const client = contentful.initClient(clientConfig);
const navigation = {
items: ['page1', 'page2']
};
// Returns a promise with navigation as content
sinon.stub(client, 'getEntries').resolves(navigation);
// Assert
return store.dispatch(actions.fetchNavigation())
.then( () => { expect(store.getActions()).toEqual(expectedActions)
})

Categories

Resources