Unexpected callTimes in async functions - javascript

I am testing with a react application with sinon, here is some backgrounds
A file uploader is created for user to upload the file to server, after the upload has completed, a dialog box will pop out showing the details of the uploaded files
Container <UpgradeApp />
class UpgradeApp extends Component {
...
onFileUpload() {
this.updateProgressBar(true); // I can only get this spy call detected
return fetch(
`/api/v1/upload-files/`,
{
credentials: 'include',
method: 'POST',
body: formObj,
headers: {'X-CSRFToken': this.props.csrf},
}
)
.then((res) => {
if (res.status >= 400) {
// error handling here...
throw new Error(`Bad server responses, status code: ${res.status}`);
}
return res.json();
})
.then((ans) => {
if (ans.success) {
this.setState({
...this.state,
dialog: {
...this.state.dialog,
submitFn: () => {
return restfulApiCall(
this.props.csrf,
'upgrade',
).then((res) => {
if (res.status >= 400) {
// error handling here...
throw new Error(`Bad server responses, status code: ${res.status}`);
}
}).catch((err) => {
console.log('in catch');
this.updateProgressBar(false); // Want to test whether this fn is called
});
}
}
}
}
});
}
...
}
Test upgrade.spec.js
The test is to check whether the function this.updateProgressBar is being executed, but I only get the called spyed in first call, but not the second one.
From the order of console log message, I can see in catch is before in then, so it is pretty sure that the catch block is being executed before the test case evaluating the function, is there any clues regarding on this? Thank you
...
const spyUpdateProgressBar = spy(upgradeAppInstance, 'updateProgressBar');
it('should hide the progress bar when upgrade is failed', function (done) {
fetchMock.post(regUploadExp, mockOkUploadHttpResponse);
fetchMock.post(regUpgradeExp, mockHttpUpgradeResponse);
upgradeAppInstance.onFileUpload().then(() => {
wrapper.update();
expect(wrapper.find(ADialog).at(0).props().open).to.equal(true);
const mockButtons2 = wrapper.find(ADialog).dive().at(0).find(Button);
expect(spyUpdateProgressBar.calledOnce).to.equal(true); // This test is passed as expected
spyUpdateProgressBar.resetHistory();
// simulate upgrade process
mockButtons2.at(1).simulate('click');
console.log('in then');
});
setImmediate(() => {
console.log(spyUpdateProgressBar.callCount);
expect(spyUpdateProgressBar.calledOnce).to.equal(true); // failed in this expect, expecting true but get false
done();
});
});

Related

axios: how to access response when getting 403 exception? [duplicate]

This may seem stupid, but I'm trying to get the error data when a request fails in Axios.
axios
.get('foo.example')
.then((response) => {})
.catch((error) => {
console.log(error); //Logs a string: Error: Request failed with status code 404
});
Instead of the string, is it possible to get an object with perhaps the status code and content? For example:
Object = {status: 404, reason: 'Not found', body: '404 Not found'}
What you see is the string returned by the toString method of the error object. (error is not a string.)
If a response has been received from the server, the error object will contain the response property:
axios.get('/foo')
.catch(function (error) {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
}
});
With TypeScript, it is easy to find what you want with the right type.
This makes everything easier because you can get all the properties of the type with autocomplete, so you can know the proper structure of your response and error.
import { AxiosResponse, AxiosError } from 'axios'
axios.get('foo.example')
.then((response: AxiosResponse) => {
// Handle response
})
.catch((reason: AxiosError) => {
if (reason.response!.status === 400) {
// Handle 400
} else {
// Handle else
}
console.log(reason.message)
})
Also, you can pass a parameter to both types to tell what are you expecting inside response.data like so:
import { AxiosResponse, AxiosError } from 'axios'
axios.get('foo.example')
.then((response: AxiosResponse<{user:{name:string}}>) => {
// Handle response
})
.catch((reason: AxiosError<{additionalInfo:string}>) => {
if (reason.response!.status === 400) {
// Handle 400
} else {
// Handle else
}
console.log(reason.message)
})
As #Nick said, the results you see when you console.log a JavaScript Error object depend on the exact implementation of console.log, which varies and (imo) makes checking errors incredibly annoying.
If you'd like to see the full Error object and all the information it carries bypassing the toString() method, you could just use JSON.stringify:
axios.get('/foo')
.catch(function (error) {
console.log(JSON.stringify(error))
});
There is a new option called validateStatus in request config. You can use it to specify to not throw exceptions if status < 100 or status > 300 (default behavior). Example:
const {status} = axios.get('foo.example', {validateStatus: () => true})
You can use the spread operator (...) to force it into a new object like this:
axios.get('foo.example')
.then((response) => {})
.catch((error) => {
console.log({...error})
})
Be aware: this will not be an instance of Error.
I am using this interceptors to get the error response.
const HttpClient = axios.create({
baseURL: env.baseUrl,
});
HttpClient.interceptors.response.use((response) => {
return response;
}, (error) => {
return Promise.resolve({ error });
});
In order to get the http status code returned from the server, you can add validateStatus: status => true to axios options:
axios({
method: 'POST',
url: 'http://localhost:3001/users/login',
data: { username, password },
validateStatus: () => true
}).then(res => {
console.log(res.status);
});
This way, every http response resolves the promise returned from axios.
https://github.com/axios/axios#handling-errors
Whole error can only be shown using error.response like that :
axios.get('url').catch((error) => {
if (error.response) {
console.log(error.response);
}
});
const handleSubmit = (e) => {
e.preventDefault();
// console.log(name);
setLoading(true);
createCategory({ name }, user.token)
.then((res) => {
// console.log("res",res);
setLoading(false);
setName("");
toast.success(`"${res.data.name}" is created`);
loadCategories();
})
.catch((err) => {
console.log(err);
setLoading(false);
if (err.response.status === 400) toast.error(err.response.data);//explained in GD
});
};
See the console log then you will understand clearly
With Axios
post('/stores', body).then((res) => {
notifyInfo("Store Created Successfully")
GetStore()
}).catch(function (error) {
if (error.status === 409) {
notifyError("Duplicate Location ID, Please Add another one")
} else {
notifyError(error.data.detail)
}
})
It's indeed pretty weird that fetching only error does not return an object. While returning error.response gives you access to most feedback stuff you need.
I ended up using this:
axios.get(...).catch( error => { return Promise.reject(error.response.data.error); });
Which gives strictly the stuff I need: status code (404) and the text-message of the error.
Axios. get('foo.example')
.then((response) => {})
.catch((error) => {
if(error. response){
console.log(error. response. data)
console.log(error. response. status);
}
})
This is a known bug, try to use "axios": "0.13.1"
https://github.com/mzabriskie/axios/issues/378
I had the same problem so I ended up using "axios": "0.12.0". It works fine for me.
You can put the error into an object and log the object, like this:
axios.get('foo.example')
.then((response) => {})
.catch((error) => {
console.log({error}) // this will log an empty object with an error property
});
It's my code: Work for me
var jsonData = request.body;
var jsonParsed = JSON.parse(JSON.stringify(jsonData));
// message_body = {
// "phone": "5511995001920",
// "body": "WhatsApp API on chat-api.com works good"
// }
axios.post(whatsapp_url, jsonParsed,validateStatus = true)
.then((res) => {
// console.log(`statusCode: ${res.statusCode}`)
console.log(res.data)
console.log(res.status);
// var jsonData = res.body;
// var jsonParsed = JSON.parse(JSON.stringify(jsonData));
response.json("ok")
})
.catch((error) => {
console.error(error)
response.json("error")
})

ElectronJS: logging method fires multiple times when don't wanted when using invoke/handle

As the title states. I have this problem with my ElectronJS project. When I try to log an output from an invoked handler, the logging happens multiple times, even though the function is run only once. What could be the cause of this?
The function is run only once, for each class object created. See GIF at the bottom to see the problem "in action".
I have tried removing the handler and listeners, but that just makes it so that the next object won't be able invoke downloadPoster..
Thanks in advance for any help.
Here is how the objects are instanciated and "processed".
// On drop
window.ondrop = async function(e) {
e.preventDefault();
body.classList.remove('file-hover');
for (var [i, file] of Object.entries(e.dataTransfer.files)) {
var x = new Movie(file.path);
await x.process();
}
return false;
};
 
Here is the function from the class in renderer.js
/**
* Download poster file for movie
*/
async downloadPoster() {
// This is the url used to search for the poster we want
var searchUrl = new URL(Config.api.searchUrl);
searchUrl.search = new URLSearchParams({
page: 1,
api_key: Config.api.key,
query: this.#movie.tags.title,
year: this.#movie.tags.year
});
// Search for poster in the main thread
await axios.get(searchUrl.href)
.then(async (resp) => {
// No results
if (resp.data.total_results <= 0) {
this.log('No poster found', 'error');
return;
}
// Invoke poster download
const result = await ipcRenderer.invoke('downloadPoster', {
src: Config.api.posterUrl + resp.data.results[0].poster_path,
dest: path.join(this.#movie.new.absolutePath, Config.api.posterFilename)
})
// This shows "undefined"
this.log(result);
})
.catch(async (err) => {
this.log('Could not search for poster: ' + err, 'error');
});
}
Here is the handler function from the main.js
// Download poster
ipcMain.handle('downloadPoster', async (event, args) => {
await axios.get(args.src, { responseType: 'stream' })
.then(async (resp) => {
const writeStream = fs.createWriteStream(args.dest);
// Write data to file
await resp.data.pipe(writeStream);
// When finished, send reply back
writeStream.on('finish', async () => {
return {
success: true,
message: 'Poster downloaded'
}
});
// On error writing, send reply back
writeStream.on('error', async (err) => {
return {
success: false,
message: 'Could not write poster to file: ' + err
}
});
})
.catch(async (err) => {
return {
success: false,
message: 'Could not download poster: ' + err
}
});
});
You may be misunderstanding invoke/handle. The whole point of using that feature is that the return value from the handler in the main process is returned by the invoke method in the renderer. So you can do this:
// main.js
ipcMain.handle('some-command', async (evt, data) => {
// do something with data, the handler can also be async
const value = await getSomeValue();
return doSomething(data, value);
}
// renderer.js
(async () => {
const invokeReturn = await ipcRenderer.invoke('some-command', data);
// invokeReturn will contain the result of doSomething(data, value) from main
})
I would need to do some debugging on your code to be sure but my instinct is that the behavior is caused by this bit of code:
// Poster download reply from the invoke above
ipcRenderer.on('downloadPosterReply', async (event, args) => {
You're setting a listener and never removing it, so with each function call there's a new handler added, causing more and more logs to appear. Thanks to invoke/handle, you don't need to use an on handler here at all.

Test the status code of a real request to an API with Jest

Hello I'm trying to test this API call but I don't know how to test for the status code of the response since it is a real (and it has to stay like that) API call and not a mock one
this is the function I'm testing:
export const getDataFromApi = (url) => {
return axios.get(url)
.then(({ data }) => data)
.catch(err => console.log(err.toString()));
}
and this is the test:
describe('Read data from API', () => {
test('Get result of the API call', (done) => {
const apiUrl = "https://rickandmortyapi.com/api/character";
getDataFromApi(apiUrl)
.then(data => {
expect(data).toBeDefined();
expect(data.results.length).toBeGreaterThan(0);
done();
});
});
});
how can I expect if the status code of data is 200 or if is another status code?
also is necessary for me to leave that done after the execution of the function? I know with call backs I have to put it but with this promise I'm not sure
Axios has a single response object returned in both the success and error paths which contains the HTTP status code. An error is raised if the response is not in the 2xx range.
You can plumb the status code as a return object from your getDataFromApi() wrapper function, but you'll probably want the full response object for other checks (like headers). I recommend getting rid of the wrapper altogether.
Without the wrapper, here's 2 different status checks using promises, one for success and one for failure:
describe('Read data from API', () => {
test('Get successful result of the API call', async() => {
const apiUrl = "https://rickandmortyapi.com/api/character";
await axios.get(apiUrl)
.then(r => {
expect(r.data).toBeDefined();
expect(r.data.results.length).toBeGreaterThan(0);
expect(r.status).toBeGreaterThanOrEqual(200);
expect(r.status).toBeLessThan(300);
})
.catch(e => {
fail(`Expected successful response`);
});
});
test('Get failure result of the API call', async() => {
const apiUrl = "https://rickandmortyapi.com/api/character-bad";
await axios.get(apiUrl)
.then(r => {
fail(`Expected failure response`);
})
.catch(e => {
if (e.response) {
expect(e.response.status).toBeGreaterThanOrEqual(400);
expect(e.response.status).toBeLessThan(500);
} else {
throw e;
}
});
});
});

Can I throw error in axios post based on response status

Is it possible to throw an error on purpose inside the .then() block in axios? For instance, if the api responds with 204 status code, could I throw an error and run the catch block?
For example:
axios.post('link-to-my-post-service', {
json-input
}).then(response => {
if (response.status === 200) {
//proceed...
}
else {
// throw error and go to catch block
}
}).catch(error => {
//run this code always when status!==200
});
EDIT
I tried this, but it didn't work:
var instance = axios.create({
validateStatus: function (status)
{
return status == 200;
}
});
axios.post('link-to-my-post-service', {input: myInput}, instance)
.then(response => {
dispatch({
type: "FETCH_SUCCESS",
payload: response.data
});
}).catch(error => {
dispatch({
type: "FETCH_FAILED",
payload: error
});
});
When I get a status code 204, still the executed block is then() block instead of the catch block.
EDIT 2
The correct answer using Ilario's suggestion is this:
var instance = axios.create({
validateStatus: function (status)
{
return status == 200;
}
});
instance.post('link-to-my-post-service', {input: myInput})
.then(response => {
dispatch({
type: "FETCH_SUCCESS",
payload: response.data
});
}).catch(error => {
dispatch({
type: "FETCH_FAILED",
payload: error
});
});
Now when the status code is not equal to 200 the catch block code is executed.
If you give a look at the GitHub Project Page you will notice following option description.
/* `validateStatus` defines whether to resolve or reject the promise for a given
* HTTP response status code. If `validateStatus` returns `true` (or is set to `null`
* or `undefined`), the promise will be resolved; otherwise, the promise will be
*/ rejected.
validateStatus: function (status) {
return status >= 200 && status < 300; // default
},
So you could create an Instance with your own configuration.
var instance = axios.create({
validateStatus: function (status) {
return status == 200;
},
});
You could also set defaults. These will be applied to every request.
axios.defaults.validateStatus = () => {
return status == 200;
};
UPDATE 1
To set the config only on a specific operation you could replace "config" with your desired values or methods.
axios.post(url[, data[, config]])
UPDATE 2
I tried this, but it didn't work.
You cannot pass the instance to axios.post(). You must call post on the new instance.
var instance = axios.create({
validateStatus: function (status) {
return status == 200;
}
});
instance.post('url', data, config);
Thank you very much for your suggestions. The answer was simpler than I expected.
I didn't want to set any default options to change the behavior of axios, so I just tried something like the code below, and it worked. Every time the code throw new Error("Error"); is executed, the catch block code is executed after that.
axios.post('link-to-my-post-service', {
json-input
}).then(response => {
if (response.status === 200) {
//proceed...
}
else {
// throw error and go to catch block
throw new Error("Error");
}
}).catch(error => {
//when throw "Error" is executed it runs the catch block code
console.log(error)
});

Karma tests undefined promise in redux

I'm writing some tests in Karma for react/redux and I can't seem to fix this bug. Every time a test is ran, it will run through the action, send the correct value to the reducer, but I always seem to get a response return of undefined. We console logged all the values up until updating the state, but once this line executes:
dispatch(duck.addLabRequest(newLab.toJS())).then(() => {
We get a .then of undefined. What could be the root cause of this?
Error
PhantomJS 2.1.1 (Mac OS X 0.0.0) creating a new lab sends the body of the form in the request FAILED
undefined is not an object (evaluating 'promise.then')
Test
describe('creating a new lab', () => {
let promise;
beforeEach(() => {
let mockAdapter = new MockAdapter(axios);
mockAdapter.onPost(labsURL).reply(201, {
lab: newLabWithID,
message: "Lab created successfully"
});
dispatch(duck.addLabRequest(newLab.toJS())).then(() => {
expect(1).to.equal(2);
})
})
})
Action
export function addLabRequest(lab) {
console.log("lab is: ")
console.log(JSON.stringify(lab));
return (dispatch) => {
axios.post(`${API_URL}/Labs`, lab, {
headers: {'Content-Type': 'application/json'}
})
.then((resData) => {
console.log("WE GOT HERE");
console.log(JSON.stringify(resData))
dispatch(addLab(resData))
})
.catch((err) => {
console.log(err.response.data.message);
});
}
}
.
.
.
Reducer
case ADD_LAB:
console.log("ADDING LAB");
console.log(JSON.stringify(action.payload));
return state.update(
'labs',
(labs) => labs.push(Immutable.fromJS(action.payload))
);
You should return inside the dispatcher. It is also a good practice to return the last dispatch in the promise:
export function addLabRequest(lab) {
return (dispatch) => {
return axios.post(`${API_URL}/Labs`, lab, {
headers: {'Content-Type': 'application/json'}
})
.then((resData) => {
return dispatch(addLab(resData));
})
.catch((err) => {
console.log(err.response.data.message);
});
}
}

Categories