Test get request in javascript - javascript

I have the next get request:
const getReq = () => {
fetch(
'https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits',
)
.then((response) => {
let status = null;
if (response.status === 200) {
status = response.status;
} else {
status = 'error';
}
if (status === 200) {
alert('success')
} else {
alert('Error')
}
return response.json();
})
.then((commits) =>{
return commits.map(i=> i.sha[0])
});
};
getReq()
I want to make 3 tests for this request:
if I get the first sha from commits: i=> i.sha[0]
check for the 200 response
check for the response that is not 200.
I wrote my first test:
import { getReq } from './getReq';
global.fetch = jest.fn(() => {
return Promise.resolve(123);
});
describe('success', () => {
test('get first sha http', async () => {
const sha = await getReq();
expect(sha).toBe(123);
});
});
But I get TypeError: Cannot read property 'then' of undefined.
How to solve the issue with the test above?

Your promise is not getting mock.
You can try to adding this on the top of the file.
jest.mock(path_of_getReq_file, () => ({
getReq: jest.fn()
}));

Related

error boundary when using hooks to display data

I try to get user data when load page using hooks(in case useEffect), but i got error said "Consider adding an error boundary to your tree" and my app getting blank.
my code looks similar like below
still configure out how to fix it, but i dont know how. coz i new using react
const [ProfileData, setProfileData] = useState({});
const [ownerId, setOwnerId] = useState('')
let ProfileID = 12
const getId = async () => {
const responseData = await getUserData();
setOwnerId(responseData.ID);
};
this is my function to get data from API
const getProfileData = () => {
setLoading(true);
getId();
const jsonData = {
ID: ProfileID,
OwnerId: ownerId
};
const headers = {
'Access-Control-Allow-Headers': '*'
};
try {
axios
.post(
config.API_SERVER + 'SearchProfile',
{
Data: jsonData ,
},
headers
)
.then(function (response) {
if (response.data.status == 'Success') {
setProfileData(response.data.Data);
setLoading(false);
} else {
setProfileData({});
alert(response.data.status);
}
})
} catch (error) {
console.log(error);
}
};
this is my hooks
useEffect(() => {
const load = async () => {
try {
await getProfileData();
} catch (err) {
throw err
}
}
load()
}, []);
and this is my return to display data
return (
<p>{ProfileData.Name}</p>
)
Since you throw an error (throw err) but you don't catch it, you can omit that part, pass the error message to state, or use react-error-boundary.
useEffect(() => {
getProfileData();
}, []);

How to improve the efficiency of an algorithm that does multiple fetch calls in terms of security and complexity?

The exercise is Write a JavaScript package that is able to:
fetch an array of URLs which contain JSON data
return their contents in a promise
I have already made a solution to this exercise, but I would like some input on how to improve this solution in terms of efficiency, security and complexity. I have also included unit tests for this solution.
index.js
const { InvalidParameterError, EmptyArrayError, InvalidURLError, FetchError, APIError } = require("./errors");
const fetch = require('node-fetch');
const validUrl = require('valid-url');
const links = [
'https://ft-tech-test-example.s3-eu-west-1.amazonaws.com/ftse-fsi.json',
'https://ft-tech-test-example.s3-eu-west-1.amazonaws.com/gbp-hkd.json',
'https://ft-tech-test-example.s3-eu-west-1.amazonaws.com/gbp-usd.json'
]
/**
* iterates through the array and fetches data from each url, includes edge cases
* #params {urls} array of URLs
* #return {Promise} promise which is an array of the data fetched from the URL in JSON format
*/
function requestMultipleUrls (urls) {
if (!Array.isArray(urls)){
console.log('Parameter passed is not in array format', urls)
return new InvalidParameterError('urls should be inside in an array')
}
if (urls.length === 0) {
console.log('Array cannot be empty', urls)
return new EmptyArrayError('Array of urls cannot be empty')
}
const promises = urls.map(async (url) => {
if ((!validUrl.isUri(url))) {
console.log('URL is in incorrect format', url)
return new InvalidURLError('Input URL is not in the correct format')
}
try {
const data = await fetch(url);
const { status } = data
if (status !== 200) {
console.log(`Your fetch call returned a status code of ${status}, check if the URL you entered is correct`)
return new APIError('Something went wrong with the URL you are trying to fetch', status)
} else {
return mapData(data)
}
} catch(error) {
console.log(error)
return new FetchError('Something went wrong during the fetch call')
}
})
return Promise.all(promises)
}
/**
* Deconstructs the data being passed to form an object that is eventually returned
* #params {data} response that is recieved from the fetch call in requestMultipleUrls
* #return {final} object that is formatted
*/
const mapData = async (data) => {
const {url, status} = data
const final = {
url,
status,
body: await data.json()
}
return final
}
requestMultipleUrls(links).then((data) => console.log(data))
module.exports = {
requestMultipleUrls
}
errors.js
class InvalidParameterError {
constructor (message) {
this.status = 400;
this.message = message;
}
}
class EmptyArrayError {
constructor(message) {
this.status = 400;
this.message = message;
}
}
class InvalidURLError {
constructor(message) {
this.status = 400;
this.message = message
}
}
class APIError {
constructor(message, status){
this.status = status,
this.message = message
}
}
class FetchError {
constructor(message) {
this.status = 502;
ths.message = message
}
}
module.exports = {
InvalidParameterError,
EmptyArrayError,
InvalidURLError,
FetchError,
APIError
}
index.spec.js
const { expect } = require('chai');
const { requestMultipleUrls } = require('../index');
const gbpUsd = require('./gbp-usd.json');
const gbpHkd = require('./gbp-hkd.json')
describe('requestMultipleUrl intended functionality', () => {
it('function should return an accumulated API response', async () => {
const dummyUrls = [
'https://ft-tech-test-example.s3-eu-west-1.amazonaws.com/gbp-usd.json',
'https://ft-tech-test-example.s3-eu-west-1.amazonaws.com/gbp-hkd.json'
]
const promise = await requestMultipleUrls(dummyUrls);
expect(promise).to.deep.equal([gbpUsd, gbpHkd]);
})
})
describe('requestMultipleUrl edge cases', () => {
it('should throw an error if the urls in the array do not match URI standards', async () => {
const dummyUrls = [
'lfkjklwfklm',
'ekefjkf;lfl;'
]
try {
await requestMultipleUrls(dummyUrls)
} catch (error) {
expect(error.message).to.eql('Input URL is not in the correct format')
}
})
it('should throw an error if URL array is empty', async () => {
const dummyUrls = []
try {
await requestMultipleUrls(dummyUrls)
} catch (error) {
expect(error.message).to.eql('Array of urls cannot be empty')
}
})
it('should throw an error if the parameter being passed is not an array', async () => {
const dummyUrls = 'https://ft-tech-test-example.s3-eu-west-1.amazonaws.com/gbp-hkd.json'
try {
await requestMultipleUrls(dummyUrls)
} catch (error) {
expect(error.message).to.eql('urls should be inside in an array')
}
})
it('should throw an error if status code is not 200', async () => {
const dummyUrls = ['https://ft-tech-test-example.s3-eu-west-1.amazonaws.com/gbp-jpy.json']
try {
await requestMultipleUrls(dummyUrls)
} catch(error){
expect(error.message).to.eql('Something went wrong with the URL you are trying to fetch')
}
})
})

Handle errors in http test requests

I am using jest to test my code:
export const getReq = async () => {
let data = '';
await fetch(
'https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits',
)
.then((response) => {
let status = null;
if (response.status === 200) {
status = response.status;
} else {
status = 'error';
}
if (status === 200) {
alert('success');
} else {
data = '';
alert('Error');
}
return response.json();
})
.then((commits) => {
data = commits.map(({ sha }: any) => sha)[0];
});
return data;
};
And now i want to test the situation when in my alert should appear: alert('Error'); and data = '';. So, for this i created this test:
test('Asynchronous return values error', async() => {
global.alert = jest.fn();
global.fetch = jest.fn().mockResolvedValueOnce({
json: () =>
Promise.reject(new Error('my error')),
status: 401,
});
const result = await getReq();
expect(global.alert).toHaveBeenCalledWith('Error');
expect(global.fetch).toHaveBeenCalledWith(
'https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits',
);
expect(result).toEqual('');
});
But when i run the test i get: Error: my error and my test does not work
What could be the issue and how to test the things that i described above?

Axios returns promise pending

i want this function to return either true or false, instead I get
/**
* Sends request to the backend to check if jwt is valid
* #returns {boolean}
*/
const isAuthenticated = () => {
const token = localStorage.getItem('jwt');
if(!token) return false;
const config = {headers : {'x-auth-token' : token}};
const response = axios.get('http://localhost:8000/user' , config)
.then(res => res.status === 200 ? true : false)
.catch(err => false);
return response;
}
export default isAuthenticated;
I tried separating them and using async/await :
const isAuthenticated = async () => {
const response = await makeRequest();
return response;
}
const makeRequest = async () => {
const token = localStorage.getItem('jwt');
const config = {headers : {'x-auth-token' : token}};
const response = await axios.get('http://localhost:8000/user' , config)
.then(res => res.status === 200 ? true : false)
.catch(err => false);
return response;
}
And still the same..
After some suggestions :
const isAuthenticated = () => {
const response = makeRequest();
return response;
}
const makeRequest = async () => {
try {
const token = localStorage.getItem('jwt');
const config = {headers : {'x-auth-token' : token}};
const response = await axios.get('http://localhost:8000/user', config);
if (response.status === 200) { // response - object, eg { status: 200, message: 'OK' }
console.log('success stuff');
return true;
}
return false;
} catch (err) {
console.error(err)
return false;
}
}
export default isAuthenticated;
First of all if.
If you are using the default promise then & catch, then the success action should be handled within the 'then' function.
axios.get('http://localhost:8000/user', config)
.then(res => console.log('succesfull stuff to be done here')
.catch(err => console.error(err)); // promise
if you want to use the async/await syntactic sugar, which I personally like it's
const makeRequest = async () => {
try {
const token = localStorage.getItem('jwt');
const config = {headers : {'x-auth-token' : token}};
const response = await axios.get('http://localhost:8000/user', config);
if (response.status === 200) { // response - object, eg { status: 200, message: 'OK' }
console.log('success stuff');
return true;
}
return false;
} catch (err) {
console.error(err)
return false;
}
}
You have to employ the use of async/await,like this:
const isAuthenticated =async () => {
const token = localStorage.getItem('jwt');
if(!token) return false;
const config = {headers : {'x-auth-token' : token}};
const response =await axios.get('http://localhost:8000/user' , config)
.then(res => res.status === 200 ? true : false)
.catch(err => false);
return response;
}

Axios: how to cancel request inside request interceptor properly?

I want to cancel the request if there's no token, so I do like this:
instance.interceptors.request.use(config => {
if (!getToken()) {
console.log("interceptors: no access token");
} else {
config.headers.Authorization = "Bearer " + getToken().accessToken;
return config;
}
});
But in negative scenario there's an error TypeError: Cannot read property 'cancelToken' of undefined.
You cannot use the token inside the interceptors but instead throw Cancel
axios.interceptors.response.use(function (response) {
throw new axios.Cancel('Operation canceled by the user.');
}, function (error) {
return Promise.reject(error);
});
Refer to this post:
https://github.com/axios/axios/issues/583
Axios v0.22.0 and higher
As per the documentation, cancellation is now pretty straightforward with the AbortController class
instance.interceptors.request.use(config => {
/* some logic */
const controller = new AbortController();
if (needToCancelRequest) {
controller.abort();
}
return {
...config,
signal: controller.signal
};
});
Browser Compatibility
You might be tempted to do a pretty concise signal: AbortSignal.abort() instead. Please, note that it is much less supported than the solution above. See AbortSignal.abort() vs new AbortController().abort() compatibility.
Axios before v0.22.0
This is a solution taken from the axios issue on github
instance.interceptors.request.use(config => {
/* some logic */
return {
...config,
cancelToken: new CancelToken((cancel) => {
if (needToCancelRequest) {
cancel('Cancel repeated request')
}
})
};
});
So for whatever reason none of these answers worked for me. Here is what did.
axiosInstance.interceptors.request.use(
function (config) {
const controller = new AbortController();
const cfg = {
...config,
signal: controller.signal,
};
controller.abort('We gotta cancel this');
return cfg;
},
function (error) {
return Promise.reject(error);
},
);
Thing I learned from this: AbortController is native to javascript/node.
I have implemented this in this way. I am not sure if this is the best solution, but for my use case is useful.
My idea is not to cancel the last request. I would like to cancel previous requests to the same endpoint, and let the last one to do his job. For that reason I keep track of the request that are being executed.
// I keep track of the current requests that are being executed
const currentExecutingRequests = {};
axios.interceptors.request.use(
(req) => {
let originalRequest = req;
if (currentExecutingRequests[req.url]) {
const source = currentExecutingRequests[req.url];
delete currentExecutingRequests[req.url];
source.cancel();
}
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
originalRequest.cancelToken = source.token;
currentExecutingRequests[req.url] = source;
// here you could add the authorization header to the request
return originalRequest;
},
(err) => {
return Promise.reject(err);
}
);
axios.interceptors.response.use(
(response) => {
if (currentExecutingRequests[response.request.responseURL]) {
// here you clean the request
delete currentExecutingRequests[response.request.responseURL];
}
return response;
},
(error) => {
const { config, response } = error;
const originalRequest = config;
if (axios.isCancel(error)) {
// here you check if this is a cancelled request to drop it silently (without error)
return new Promise(() => {});
}
if (currentExecutingRequests[originalRequest.url]) {
// here you clean the request
delete currentExecutingRequests[originalRequest.url];
}
// here you could check expired token and refresh it if necessary
return Promise.reject(error);
}
);
As of Axios v0.22.0 an AbortSignal is the recommended way to cancel from a request interceptor.
axios.interceptors.request.use(
(requestConfig) => {
/* some logic */
return {
...requestConfig,
signal: AbortSignal.abort()
};
}
},
(error) => {
return Promise.reject(error);
}
);
#Kirill Taletski's answer solve this perfectly, but add one line:
const CancelToken = Axios.CancelToken;
then ,it gonna be like this :
instance.interceptors.request.use(config => {
/* some logic */
const CancelToken = Axios.CancelToken;
return {
...config,
cancelToken: new CancelToken((cancel) => cancel('Cancel repeated request'))
};
});
here is the solution
import axios from 'axios';
const CancelToken = axios.CancelToken;
let cancel;
axios.interceptors.request.use((config) => {
if (cancel) {
cancel(); // cancel request
}
config.cancelToken = new CancelToken(function executor(c)
{
cancel = c;
})
return config
}, function (error) {
return Promise.reject(error)
});
My solution based on https://stackoverflow.com/a/64228288/2051938
axios.ts
const axiosInstance = axios.create({ baseURL: apiBaseUrl });
axiosInstance.interceptors.request.use(
req => {
const originalRequest = req;
const cancelUniqId = (originalRequest.cancelToken as unknown) as string;
if (Object.hasOwnProperty.call(currentExecutingRequests, cancelUniqId)) {
const source = currentExecutingRequests[cancelUniqId];
delete currentExecutingRequests[cancelUniqId];
source.cancel();
}
if (cancelUniqId) {
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
originalRequest.cancelToken = source.token;
currentExecutingRequests[cancelUniqId] = source;
}
return originalRequest;
},
err => {
return Promise.reject(err);
}
);
axiosInstance.interceptors.response.use(
response => {
for (const key of Object.keys(currentExecutingRequests)) {
if (currentExecutingRequests[key].token === response.config.cancelToken) {
delete currentExecutingRequests[key];
break;
}
}
return response;
},
error => {
const { response } = error;
if (axios.isCancel(error)) {
return new Promise(() => {
//
});
}
for (const key of Object.keys(currentExecutingRequests)) {
if (currentExecutingRequests[key].token === response.config.cancelToken) {
delete currentExecutingRequests[key];
break;
}
}
return Promise.reject(error);
}
);
export { axiosInstance };
Usage:
axiosInstance.request({
url: "some/req/path",
method: "POST",
params: {...},
data: {...},
cancelToken: "someUniqRequestID" // <-- IMPORTANT!
})
as a result, all requests with someUniqRequestID token will be cancelled when previous request with SAME cancelToken was not finished before.
This works for me for axios 0.20.0:
const interceptorRequestConfig = (config) => {
if (sourceRequest[config.url]) {
sourceRequest[config.url].cancel('Automatic cancellation')
}
const axiosSource = axios.CancelToken.source()
sourceRequest[config.url] = { cancel: axiosSource.cancel }
config.cancelToken = axiosSource.token
return config
}
credit for the idea: https://stackoverflow.com/a/66701130/8840359

Categories

Resources