how to mock multiple fetch calls being made in the same function - javascript

I have a function, as below, that performs 2 fetch calls within the same function
getNames() {
var qs = require("qs");
fetch(<URL>,
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
},
body: qs.stringify({
firstName: this.state.firstName,
lastName: this.state.lastName
})
})
.then(response => response.json()).then((data) => {
console.log(data)
});
var url = new URL(<someURL>)
fetch(<someURL>).then(response => response.json()).then((data) => {
...do something...
}
})
.catch(error => {
alert("no response");
console.log(error);
});
}
I am testing this using Jest and Enzyme on React. The above belongs to the GetName component. Below is my test case:
describe('getName', () => {
const wrapper = shallow(<GetName />).instance();
beforeEach(() => {
global.fetch.resetMocks();
});
it('positive flow', () => {
global.fetch.mockResolvedValue(
new Response(JSON.stringify({data: "mockData"}))
);
const state = {
firstName: "don",
lastName: "Lee"
};
wrapper.setState(state);
const actualValue = wrapper.getNames();
expect(actualValue).toBeUndefined();
});
});
Once I do this, I get an error that TypeError: body used already for: undefined
I understand that the fetch here is being used for the POST call, but how do I make sure that I can mock both the fetch calls within the function?
I have also tried fetch.mockResponse and fetch.mockResponses and also fetch.mockResponseOnce. None of them seem to help me mock them more than once and I get this error with all functions mentioned.
Is there any other way to mock both the fetch calls?

Related

Updated object not being returned properly in nextjs

So basically I'm working on a nextjs app which uses authentication. I have a 2 functions which I run on every page load. The first checks if jwt cookies exist and calls another function to validate the tokens if they don't exist. This function is ran from wrapper.getServerSideProps and is passed in the context as ctx. This function works as intended.
export const checkServerSideCookie = (ctx) => {
const access = getCookie("access", ctx.req);
const refresh = getCookie("refresh", ctx.req);
if (access && refresh) {
return checkAuthentication(access, refresh);
} else return { isAuthenticated: false, token: null };
};
The second function is the token validator and this is where the issue arises. I have an object which I intended to update if the validation is successful and leave alone if it isn't. Here is the function
export const checkAuthentication = (access, refresh) => {
const obj = {
isAuthenticated: false,
token: null,
};
const body = JSON.stringify({ token: access });
axios
.post("http://localhost:8000/api/jwtoken/verify/", body, {
headers: {
"Content-Type": "application/json",
},
})
.then((res) => {
obj.isAuthenticated = true;
obj.token = access;
})
.catch((err) => {
// call new token function using refresh
console.log("it doesnt work");
});
return obj;
};
The issue is is that the .then does update the object, and when I console.log(obj) in the .then it shows the proper obj to return, however when I return the obj it still holds the initial values of false and null. I don't understand what the issue is. I try doing the return in the .then itself but it throughs this error
TypeError: Cannot destructure property 'isAuthenticated' of 'Object(...)(...)' as it is undefined.
What is the issue here? It all seems good but the updated obj isn't returned.
axios.post is async, you're returning the obj before it gets filled with data from the api response, you can use async/await to solve that :
export const checkAuthentication = async (access, refresh) => {
const obj = {
isAuthenticated: false,
token: null
};
const body = JSON.stringify({ token: access });
try {
const res = await axios.post("http://localhost:8000/api/jwtoken/verify/", body, {
headers: {
"Content-Type": "application/json"
}
});
obj.isAuthenticated = true;
obj.token = access;
} catch (e) {
// do something with the error
// call new token function using refresh
console.log("it doesnt work");
}
return obj;
};
usage (checkAuthentication now return a promise ) :
checkAuthentication(a, b).then((obj) => {
console.log(obj);
});
When you call checkAuthentication it immediately returns the obj with the default properties. You have an asynchronous operation specified in your function, however you don't wait until it's done. You'd have to rebuild your function the following way:
export const checkAuthentication = (access, refresh) => {
const obj = {
isAuthenticated: false,
token: null,
};
const body = JSON.stringify({ token: access });
return new Promise((resolve, reject) => {
axios
.post("http://localhost:8000/api/jwtoken/verify/", body, {
headers: {
"Content-Type": "application/json",
},
})
.then((res) => {
resolve({
isAuthenticated: true,
token: access
})
})
.catch((err) => {
// call new token function using refresh
console.log("it doesnt work");
reject();
});
});
};
and then call your function the following way:
checkAuthentication(access, refresh)
.then(console.log)
.catch(console.log)
You, of course, have multiple options to make your function cleaner, such as by using async/await etc, but this should give you a quick overview of what is wrong.

Send surveyjs result to API

I am trying to send surveyjs results to my API.
In mounted(), I make a GET request with vue-resource, get questions from my dB and then set surveyjs. To send the results, I tried to use this.$http.post, in the surveyJS onComplete function, but I got Cannot read property 'post' of undefined. Also, I tried to put a watch on the result variable, but it did not work.
mounted() {
this.$http
.get("myAPI")
.then(res => res.json())
.then(questions => {
this.questions = questions;
this.survey = new SurveyVue.Model(this.questions.pesquisa);
this.survey.locale = "pt";
this.survey.onComplete.add(function(survey) {
this.result = survey.data;
this.$http
.post(
`myAPI`,
this.result,
{ headers: { "Content-Type": "application/json" } }
)
.then(response => {
console.log(response);
UIkit.notification({
message: "Success",
pos: "top-center",
status: "success"
});
})
.catch(error => {
console.log(error);
UIkit.notification({
message: "Erro",
pos: "top-center",
status: "danger"
});
});
});
})
.catch(error => {
console.log(error);
UIkit.notification({
message: "Error",
pos: "top-center",
status: "danger"
});
});
}
To get access to this inside the onComplete.add()'s parameter, you could replace your regular function with an arrow function:
this.survey.onComplete.add(survey => {
this.result = survey.data;
/* rest of your code... */
})
An alternative is to place this into a variable, which can be used to access the outer this:
const that = this;
this.survey.onComplete.add(function(survey) {
that.result = survey.data;
/* rest of your code... */
})
Read more about this.
The gist of it is that inside the the function, the function's this overrides the component's this, unless it's an arrow function, which purposefully doesn't have a this so the outside one is available.

axios get request return request failed with error 400

I need help on solving this issue. I am new with react native and javascript. Now I am trying to hook up the react native application with API. This process require me to get the token first by axios.post before I can do axios.get to fetch the data.
Long story short, below is my code snippet for both.
... // code
const TOKEN_URL = 'https://test.co/testing/tokens'
const DATA_URL = 'https://test.co/testing/data/page1'
const getToken = () => {
axios.post(TOKEN_URL, {
email: 'email',
password: 'password',
role: 'user'
})
.then((response) => {
//console.log(response.data.token);
return response.data.token;
})
.catch((error) => {
console.log(error);
});
};
//'export' here is for use in other code: example onPress function
export const fetchDriver = () => {
const config = {
headers: {
'Bearer': getToken()
}
};
axios.get(DRIVER_URL, config)
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error);
});
};
My expected console log would be something like this
{
"timestamp": 1510038433,
"verb": "GET",
"object": "student",
"data": {
"age": "12",
"id": "90000",
"name": "Test Student",
"emergencyName": "asd",
"createdAt": "2017-10-04T05:39:39+00:00"
}
}
But I keep getting error saying Request failed with status code 400
I am using Expo to develop this app.
Detail on the error is like this
- node_modules/axios/lib/core/createError.js:16:24 in createError
- node_modules/axios/lib/core/settle.js:19:6 in settle
- node_modules/axios/lib/adapters/xhr.js:78:13 in handleLoad
- node_modules/event-target-shim/lib/event-target.js:172:43 in dispatchEvent
- node_modules/react-native/Libraries/Network/XMLHttpRequest.js:540:23 in
setReadyState
- node_modules/react-native/Libraries/Network/XMLHttpRequest.js:381:25 in
__didCompleteResponse
- node_modules/react-native/Libraries/vendor/emitter/EventEmitter.js:182:12 in
emit
- node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:306:47 in
__callFunction
- node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:108:26 in
<unknown>
- node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:269:6 in
__guard
- node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:107:17 in
callFunctionReturnFlushedQueue
I do not have any authorization to edit the api/server if the error comes from there.
Please help me if there is any point that I have been missing out in the snippet.
Thank you for your help and suggestion.
sidenote, you forgot to return inside getToken
I'll just give you back story on why this happens.
Promises are asynchronous, so is your axios call. Therefore you need to somehow wait for first call result. Otherwise if you put const a = axiosCall() and try to use it right away the a value would be Pending (not a string tho).
For that you can use promises or async/await. I'll show you proper approach with promises. I've just copied your code and refactored it a bit. Also remember that driver is still a promise so you need to handle it as other things.
const getToken = () => {
axios.post(TOKEN_URL, {
email: 'email',
password: 'password',
role: 'user'
})
.then((response) => {
//console.log(response.data.token);
return response.data.token;
})
.catch((error) => {
console.log(error);
});
};
//'export' here is for use in other code: example onPress function
export const fetchDriver = () => {
const config = {
headers: {
'Bearer': getToken()
}
};
axios.get(DRIVER_URL, config)
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error);
});
};
You are not chaining your requests. You have to wait till you get the token to be able to use it.
Something like this
getToken
const getToken = () => {
return axios.post(TOKEN_URL, {
email: 'email',
password: 'password',
role: 'user'
})
.then((response) => {
//console.log(response.data.token);
return response.data.token;
})
.catch((error) => {
console.log(error);
});
};
fetchDriver
export const fetchDriver = () => {
return getToken().then(token => {
const config = {
headers: {
'Bearer': token
}
};
return axios.get(DRIVER_URL, config)
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error);
});
});
}
You need to wait until token api get return response and after that You need make second api call with token
change like this
getToken : change to async function
const async getToken = () => {
axios.post(TOKEN_URL, {
email: 'email',
password: 'password',
role: 'user'
})
.then((response) => {
//console.log(response.data.token);
return response.data.token;
})
.catch((error) => {
console.log(error);
});
};
fetchDriver : add await while calling getToken function
export const fetchDriver = () => {
const config = {
headers: {
'Bearer': await getToken()
}
};
axios.get(DRIVER_URL, config)
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error);
});
};

React testing onSubmit using axios

I recently started testing my React app. However, I stumbled when dealing with submitting forms. My test covers most of the lines but misses out on actual part of submit form method.
LoginForm.js - submit form
const userLoginData = {
userId : this.state.userId,
password : this.state.password,
userType : this.state.userType
};
axios({
data : JSON.stringify(userLoginData),
type : 'post',
url : Constant.BASE_URL_SERVER+'/rest/login',
headers : {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
cache : false
})
.then(function (response) {
//alert("Form Submitted.");
this.setState({isLoggedIn : true});
this.setState({loginResponse : "Login Success!"});
if(this.state.userType === 'Customer'){
...
login_form-test.js
describe('testing form submission onSubmit', () => {
const testData = {
userId: '00000000',
password: 'SamplePassword0',
userType: 'Customer',
validForm: true,
}
it('should submit form onSubmit()', () => {
const mountedComponentHandle = mount(<LoginForm {...testData}/>);
const onSubmitForm = sinon.spy(
mountedComponentHandle.instance(),
'handleSubmitForm'
);
mountedComponentHandle.update();
const formHandle = mountedComponentHandle.find('form');
expect(formHandle.length).toBe(1);
formHandle.simulate('submit');
expect(onSubmitForm.called).toBe(true);
});
});
Please suggest on how to test .then() and .catch() of axios.
Thanks.
Key here is to make your code "testable". Separating responsibility helps to make your code more testable, readable and easy to maintain. In your case logic to post data over an API lies in some service which will handle api requests for your app, and you can test it separately.
Coming back to your question, I am providing you one of the possible solutions for testing async calls in your case:
// apiGateway.js
const postData = (url, data) => (
axios({
data: JSON.stringify(data),
type: 'post',
url: BASE_URL_SERVER + url,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
cache: false
})
);
Again you can test above code separately.
// myAppApi.js
const postLoginForm = (data, callback, errorCallback) => {
return postData('/rest/login', data)
.then((response) => callback(response.data))
.catch((error) => errorCallback(error))
};
// myAppApi.test.js
// import * as myAppApi from '../myAppApi'
it('should call callback when response is successful', async () => {
const mockResponse = {};
const mockRequestData = {};
const mockSuccessCallback = jest.fn();
const mockErrorCallback = jest.fn();
spyOn(myAppApi, 'postLoginForm').and.returnValue(Promise.resolve(mockResponse));
await myAppApi.postLoginForm(mockRequestData, mockSuccessCallback, mockErrorCallback);
expect(mockSuccessCallback).toHaveBeenCalled();
});
it('should call error callback when response is failed', async () => {
const mockRequestData = {};
const mockSuccessCallback = jest.fn();
const mockErrorCallback = jest.fn();
spyOn(myAppApi, 'postLoginForm').and.returnValue(Promise.reject());
await myAppApi.postLoginForm(mockRequestData, mockSuccessCallback, mockErrorCallback);
expect(mockErrorCallback).toHaveBeenCalled();
});
In above tests you can use different mocking methods or libraries.
And finally your component will look something like this
// LoginForm.js
class LoginForm extends React.Component {
onSuccessfulLogin(responseData) {
//.. success logic here
}
onFailedLogin(error) {
//.. error logic here
}
onSubmitForm(event) {
postLoginForm(this.state.data, this.onSuccessfulLogin, this.onFailedLogin)
}
}
As you can see separating out logic helps in testing. Further it will save you from ending up with component with tons of code in it. You can test your component for its state and presentation.
Hope this answers your question!

Why does this mock api not work as expected?

I'm trying to test this simple api module:
import fetch from 'isomorphic-fetch';
export const getJson = (endpoint: string) => {
const options = { credentials: 'include', method: 'GET' };
return fetch(endpoint, options)
.then(response => response.json()
.then(json => {
if (response.ok) return json;
return Promise.reject(json.errors);
})
)
.catch(error => {
if (error.constructor === Array) return error;
return [error.message];
});
};
With this test, where I'm mocking fetch:
import { getJson } from '../api';
const mockResponse = (status, statusText, response) => {
return new window.Response(response, {
status: status,
statusText: statusText,
headers: {
'Content-type': 'application/json'
}
});
};
describe('api middleware', () => {
describe('getJson', () => {
it('should return the response on success', () => {
const expected = { data: ['data'], meta: {} };
const body = JSON.stringify(expected);
window.fetch = jest.fn().mockImplementation(() =>
Promise.resolve(mockResponse(200, null, body)));
return getJson('http://endpoint').then(actual => expect(actual).toEqual(expected));
});
});
});
But the test fails with:
Expected value to equal:
{"data": ["data"], "meta": {}}
Received:
["Unexpected end of JSON input"]
Difference:
Comparing two different types of values:
Expected: object
Received: array
I've not been able to figure out why this isn't working. Why am I receiving the "Unexpected end of JSON input" error? And how do I successfully mock fetch locally in a test? In this medium post it's done in basically the same way..
So apparently the test was still using the global fetch library, and not my patched version. The solution was to:
Remove the 'isomorphic-fetch' mock (in __mocks__ at the root of the project).
Import 'isomorphic-fetch' once at the root of my project with import 'isomorphic-fetch;
Remove the 'isomorphic-fetch' import at the top of my api module (since it's already imported at the entrypoint
Update the test to:
test:
// to make the Response constructor available
import 'isomorphic-fetch';
import { getJson } from '../api';
describe('api middleware', () => {
describe('getJson', () => {
beforeEach(() => {
window.fetch = jest.genMockFunction();
});
it('should return the response on success', () => {
const expected = { data: ['data'], meta: {} };
const body = JSON.stringify(expected);
const init = { status: 200, statusText: 'OK' };
window.fetch.mockReturnValueOnce(Promise.resolve(new Response(body, init)));
return getJson('http://endpoint').then(actual => expect(actual).toEqual(expected));
});
});
});
Most probably because your getJson function does not use the global (window) fetch.
The way I would suggest doing it is to use Dependency Injection (DI); make getJson retrieve the the "http request" library/function (in your case fetch) and in your tests, create a mock function which is injected. The mock function will return the data that you want as part of testing.

Categories

Resources