response undefined when using jest mock to test axios in vue - javascript

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('/')
...

Related

Test then() inside of a promise API call jest

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',
);

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 to mock function, that is called in "then" in axios promise?

I have a function, that is fetching data from the backend. When fetch is successful, it extracts one value from the response, and then call another function (parseAllRecordsData), that is converting the value into other value. I'm trying to test this function, but after mocking parseAllRecordsData function, it's still trying to call original function (and throws errors from that function).
In other tests jest.fn or jest.spy is working correctly, but when I'm trying to mock function that is used in "then" it's not.
export function fetchAllRecordsData(payload) {
const url = `/apis/${payload.link.split('apis/')[1]}`;
return axios.get(url)
.then(({ data }) => {
if (data && data._embedded) {
const parsedData = data._embedded['taxonomies:entry'];
const arrayData = parseAllRecordsData(parsedData, payload);
return { data: List(arrayData) };
}
return { data: List([]) };
})
.catch((error) => ({ error }));
}
And my test:
describe('fetchAllRecordsData', () => {
const mockedPayload = {
link: 'apis/ok_link',
};
beforeAll(() => {
jest.spyOn(LegalListRecordsApi,'parseAllRecordsData').mockReturnValue(['test']);
});
it('test', async () => {
const test = await LegalListRecordsApi.fetchAllRecordsData(mockedPayload);
expect(test).toEqual(1);
});
});
When it's called like this, parseAllRecordsData calls real function, and throws the error, because mocked Axios response doesn't have some values that parsing function use. I'm only interested in return value, not calling this function.
jest.spyOn(LegalListRecordsApi,'parseAllRecordsData').mockReturnValue(['test']); mocks the module export for parseAllRecordsData.
This doesn't have any effect on fetchAllRecordsData because it is in the same module as parseAllRecordsData and is calling it directly.
ES6 modules supports cyclic dependencies so you can import a module into itself.
Import the module into itself and use the module to call parseAllRecordsData:
import * as LegalListRecordsApi from './LegalListRecordsApi'; // import module into itself
export function fetchAllRecordsData(payload) {
const url = `/apis/${payload.link.split('apis/')[1]}`;
return axios.get(url)
.then(({ data }) => {
if (data && data._embedded) {
const parsedData = data._embedded['taxonomies:entry'];
const arrayData = LegalListRecordsApi.parseAllRecordsData(parsedData, payload); // use the module
return { data: List(arrayData) };
}
return { data: List([]) };
})
.catch((error) => ({ error }));
}
...and the call will be mocked when you mock the module export for parseAllRecordsData.
export function fetchAllRecordsData(payload, axiosInterface = axios) {
return return axiosInterface.get(url)
. then(({ data }) => {
// your code
})
.catch((error) => ({ error }))
}
So, you need create mock object with method get, method get should return promise.

How to verify console.log was called in componentDidMount using Jest and Enzyme?

I'm trying test for cases when my axios call does not get an HTTP response of 200. When axios does not get a successful response, it throws an error. I want to verify that console.log gets called twice in this case.
Here's a snippet of the class I'm testing:
class App extends React.Component {
...
async componentDidMount() {
let url = "/api/info/tmp"
try {
let response = await axios.get(url);
...do stuff
this.setState(...);
} catch (e) {
console.log("Could not get " + url);
console.log(e);
}
}
...
}
And here's a snippet of my jest test
let mockAxios = new MockAdapter(axios);
...
describe("App - componentDidMount() behavior test", () => {
beforeEach(() => {
app = shallow(<App />);
})
afterEach(() => {
app = undefined;
mockAxios.reset();
});
...
describe("Get " + url + " HTTP response status is not 200", () => {
beforeAll(() => {
mockAxios.onGet(url).reply(302, mockData);
});
it("Does not set state regardless of response body", () => {
console.log = jest.fn();
const state = app.state();
expect(console.log).toHaveBeenCalledTimes(2);
expect(state.solutions).toEqual({});
expect(state.username).toEqual("");
});
});
});
I know the console.log = jest.fn() bit is doing something because the console does not log the fake error anymore when I set it. However, the test fails because Expected mock function to have been called two times, but it was called zero times.
I've tried moving the console.log = jest.fn() into the "beforeEach", "beforeAll", and as a global variable.
UPDATE
I am pretty sure it's something to do with all the async that is going on.
If I do this:
it("Does not set state regardless of response body", async () => {
console.log = jest.fn();
await app.instance().componentDidMount();
expect(console.log).toHaveBeenCalledTimes(2);
const state = app.state();
expect(state.solutions).toEqual({});
expect(state.username).toEqual("");
});
Then the test still fails but my reason changed: Expected mock function to have been called two times, but it was called four times. Now I just got to figure out why it was called four times not twice.
UPDATE 2
I figured out why console.log was being called 4 times! Now I just need to figure out how I should refactor my tests.
If I comment out my jest mock, and even the whole unit test
it("Does not set state regardless of response body", async () => {
//const state = app.state();
//expect(state.solutions).toEqual({});
//expect(state.username).toEqual("");
//expect(console.log).toHaveBeenCalledTimes(2);
});
Then I can count in my console that there are already indeed two different console.log calls. shallow(<App />) must be already calling componentDidMount() or something. When I add app.instance().componentDidMount(), I can visually see that it is logging 4 times.
Updated Answer
Since it looks like you already know what you're doing with mocks, perhaps the issue has to do with componentDidMount().
I believe that your call to shallow(<App />) will already call App's componentDidMount() one time (which means your console.log will get called twice there).
Then, you subsequently call app.instance().componentDidMount() - that is, you call componentDidMount() again (which means your console.log will get called twice there again).
So, total... 4 calls to console.log.
Hope that points you in the right direction...
Original Answer
Actually, your question looks quite similar to [this StackOverFlow question on how to "How to mock console when it is used by a third-party library?"
You can use Jest mock functions to spyOn the global.console object.
For example, your test may look like this:
// Setup jest to spy on the console
const consoleSpy = jest.spyOn(global.console, 'log')
describe('App - componentDidMount() behavior test', () => {
beforeEach(() => {
jest.resetAllMocks() // reset your consoleSpy state back to initial
app = shallow(<App />)
})
...
it('Does not set state regardless of response body', () => {
const spy = jest.spyOn(global.console, 'log')
const state = app.state()
expect(consoleSpy).toHaveBeenCalledTimes(2)
expect(state.solutions).toEqual({})
expect(state.username).toEqual('')
})
...
Ideally, you'd move your API call outside of componentDidMount and into its own class method. Thay way it can be manually invoked from a lifecycle method or from an event callback. Also, you should anticipate the response to affect your UI state in some fashion (example: displaying a message to the user that the request failed and to try again).
The following example can be done with .then/.catch instead of async/await. Either way, you're working with Promises that are asynchronous and therefore they need asynchronous tests.
Note: The below assumes disableLifecycleMethods is true in the enzyme adapter. Also, just testing state changes (or a console.log) is a bit superfluous; instead, you would test if a component is rendered based upon the current state.
Working example: https://codesandbox.io/s/939w229l9r (includes both end to end and integration tests --- you can run the tests by clicking on the Tests tab located near the bottom left of the sandbox)
App.js (this will be a container that holds all relevant state and disperses it to its children as needed)
import React, { Component } from 'react';
class App extends Component {
state = = {
error: "",
isLoading: true,
solutions: {},
username: ""
};
componentDidMount() {
this.fetchData("/api/info/tmp");
}
fetchData = async (url) => {
try {
const res = await axios.get(url);
...do stuff
this.setState({
error: "",
isLoading: false,
solutions: res.data.solutions,
username: res.data.username
});
} catch (err) {
this.setState({
error: err,
isLoading: false,
solutions: {},
username: ""
});
}
}
render() { ... }
}
App.test.js (this assumes you'd want an end to end test)
import { shallow } from 'enzyme';
import App from './App';
const timeout = () =>
new Promise(resolve => {
setTimeout(() => {
resolve();
}, 2000);
});
const initialState = {
error: "",
isLoading: true,
solutions: {},
username: ""
};
describe("App", () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<App />);
wrapper.setState({ ...initialState });
});
afterAll(() => {
wrapper.unmount();
});
it("sets data to state based upon successful API call", async () => {
wrapper.instance().fetchData("/api/info/tmp");
await timeout();
wrapper.update();
expect(wrapper.state('isLoading')).toBeFalsy();
expect(wrapper.state('solutions')).toEqual({ somedata });
expect(wrapper.state('username')).toEqual("Some User");
});
it("displays an error upon unsuccessful API call", async () => {
wrapper.instance().fetchData("/api/bad/url");
await timeout();
wrapper.update();
expect(wrapper.state('isLoading')).toBeFalsy();
expect(wrapper.state('solutions')).toEqual({});
expect(wrapper.state('username')).toEqual("");
expect(wrapper.state('error')).toEqual("No data found.");
});
});
App.test.js (this assumes you'd want an integration test)
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import React from "react";
import { shallow } from "enzyme";
import App from "../App";
const solutions = [{ ... }, { ... }];
const username = "Some User"
const mockAxios = new MockAdapter(axios);
const initialState = {
error: "",
isLoading: true,
solutions: {},
username: ""
};
describe("App", () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<App />);
wrapper.setState({ ...initialState });
});
afterEach(() => {
mock.reset();
});
afterAll(() => {
mock.restore();
wrapper.unmount();
});
it("displays an error upon unsuccessful API call", async () => {
try {
mockAxios.onGet("/users").networkErrorOnce();
await axios.get("users");
} catch (err) {
const error = err.toString();
wrapper.setState({
error,
isLoading: false,
solutions: {},
username: ""
});
wrapper.update();
expect(wrapper.state('isLoading')).toBeEqual(error);
expect(wrapper.state('isLoading')).toBeFalsy();
expect(wrapper.state('solutions')).toEqual({});
expect(wrapper.state('username')).toEqual("");
}
});
it("sets data to state based upon successful API call", async () => {
try {
mockAxios.onGet("/users").reply(200, { solutions, username });
const res = await axios.get("users");
wrapper.setState({
error: "",
isLoading: true,
solutions: res.data.solutions,
username: res.data.username
});
wrapper.update();
expect(wrapper.state('isLoading')).toBeFalsy();
expect(wrapper.state('solutions')).toEqual(solutions);
expect(wrapper.state('username')).toEqual(username);
} catch (e) {
console.log(e);
}
});
});
I figured it out! Kind of... I am not certain why it works like this, but setting the mock in the actual "it" did not work.
The solution was making a beforeEach and afterEach
describe("Get " + url + " HTTP response status is not 200", () => {
beforeAll(() => {
mockAxios.onGet(url).reply(302, mockData);
});
beforeEach(() => {
console.log = jest.fn();
});
afterEach(() => {
jest.resetAllMocks();
});
it("Does not set state regardless of response body", async () => {
const state = app.state();
expect(state.solutions).toEqual({});
expect(state.username).toEqual("");
expect(console.log).toHaveBeenCalledTimes(2);
});
});

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