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);
});
}
}
Related
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")
})
I am testing a Vue component's method that performs a basic fetch, and updates a data value in the .finally() block of that fetch. I can confirm that my test reaches that .finally() block, but the data value is never updated.
My method is:
updateProfile () {
fetch(updateProfileEndPoint, {
method: 'POST',
body: {email: test#test.com, id: 1234, name: 'bob},
})
.catch((error) => {
this.errorField = true;
})
.finally(() => {
this.profileUpdated = true;
});
In my Jest test, I have:
const wrapper = mount(ProfileComponent, { store,
data () {
return {
profileUpdated: false,
};
},
});
global.fetch = jest.fn(() =>
Promise.resolve({
profileUpdate: 'complete',
})
);
wrapper.vm.updateProfile();
expect(wrapper.vm.profileUpdated).toBe(true);
However, profileUpdated remains false. Strangely, if I console.log(this.profileUpdate) in that method, the updated value true does log. However, my tests still receive false.
Your assertion occurs before the asynchronous fetch call actually completes.
One solution is to return the fetch result (a Promise) from updateProfile(), allowing the test to await the call:
// MyComponent.vue
export default {
methods: {
updateProfile() {
return fetch(...).catch(...).finally(...)
} 👆
}
}
// MyComponent.spec.js 👇
it('updateProfile() sets profileUpdated flag', async () => {
const wrapper = mount(...)
👇
await wrapper.vm.updateProfile()
expect(wrapper.vm.profileUpdated).toBe(true)
})
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();
});
});
I'm writing a REST API and trying to correctly handle any errors.
When the API call succeeds, the the success object is returned to the calling function and the response is send to the client. But if an error occurs, I want to return the error to the calling function so I can send an error message to the client.
router.delete('/project', (req, res) => {
return DeleteProject(userId, projectId)
.then((response) => {
//handle response
});
});
DeleteProject: (userId, projectId) => {
return deleteProject(userId, projectId)
.then((response) => {
return response
})
.catch((error) => {
console.log('Error in DeleteProject:', error) // This happens.
return error; // this doesn't happen.
})
},
function deleteProject(userId, projectId) {
return Project.deleteOne( ... delete the project... )
.then((response) => {
return response
})
.catch((error) => {
return error
})
}
The .catch(error) in the middle function above, DeleteProject(), gets triggered when an error occurs (ie, the console log happens), but the return doesn't make it's way back to the router.
How can I return the error to be handled by the router?
You can simply remove catch methods from the other two functions, and put the catch function in the router itself. Then the error will itself propagate to your router function
router.delete('/project', (req, res) => {
return DeleteProject(userId, projectId)
.then((response) => {
//handle response
}).catch(() => {
// Add catch function here. Any error in "DeleteProject" and "deleteProject" will propagate to here
})
});
DeleteProject: (userId, projectId) => {
return deleteProject(userId, projectId)
.then((response) => {
return response
});
// Remove catch function
},
function deleteProject(userId, projectId) {
return Project.deleteOne( ... delete the project... )
.then((response) => {
return response
});
// Remove catch function
}
To propagate errors through promise chains you need to throw them. In your catch handler, when you return the error rather than throwing it, you'e setting the (successfully) resolved value of the promise to be the error.
I want to add a new object to a table using Objection.js and knex.js. I wrote this code:
const Provider = require('../models/provider);
Provider
.query()
.where('ProviderName', name)
.then((data) => {
if (data.length === 0) {
Provider
.query()
.insert({ ProviderName: name,
ProviderWebSite: webSite,
ProviderContact: contact,
ProviderStatus: status })
.then(() => {
res.render('results', { result: req.body });
});
} else {
res.render('results1');
}
})
.catch(() => {
res.render('404');
});
The problem that the page still reload and I didn't get the page results and the table still empty.
You seem to be missing at least one return from your promise chain, but to get the error why this fails you would need to printout what error was thrown when your catch block is emitting 404.
Probably this gives you a bit more info:
const Provider = require('../models/provider');
Provider
.query()
.where('ProviderName', name)
.first()
.then(result => {
if (!result) {
return Provider
.query()
.insertAndFetch({
ProviderName: name,
ProviderWebSite: webSite,
ProviderContact: contact,
ProviderStatus: status
});
}
return result;
})
.then(() => {
// before rendering views, making sure that correct data was fetched
res.send('result ' + JSON.stringify(result, null, 2));
})
.catch(err => {
// print out error to be able to debug your problem
res.send('404 ' + err.stack);
});