React Jest tests failing with MSW - javascript

I have created a basic React application and configured MSW following the instructions to setup for unit tests (node environment) and browser
The App component uses a custom hook useFormSubmission and renders a simple form with a username text field and a submit button. The form's submit handler uses the callback returned by the custom hook.
All the unit tests are failing at the moment. I can see MSW receiving the requests but I don't see any response coming back. The console logs the reducer state Status: pending but it does not go beyond that (seems like the response is swallowed/lost?) Strangely enough, the app works when running with development server npm start.
const useFormSubmissionReducer = (state, action) => {
switch (action.type) {
case "start":
return { status: "pending" };
case "resolved":
return { status: "resolved", data: action.data };
case "rejected":
return { status: "rejected", error: action.error };
default:
throw new Error(`Unsupported type: ${action.type}`);
}
};
const handleResponse = async (response) => {
const data = await response.json();
if (response.status >= 200 && response.status <= 299) {
return Promise.resolve(data);
} else {
return Promise.reject(data);
}
};
const useFormSubmission = () => {
const [state, dispatch] = useReducer(useFormSubmissionReducer, {
status: "idle",
data: null,
error: null,
});
const callback = useCallback((request) => {
const payload = JSON.stringify(request);
console.log("Dispatching: ", request);
dispatch({ type: "start" });
fetch("/api/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: payload,
})
.then(handleResponse)
.then(
(data) => {
console.log("Data: ", data);
dispatch({ type: "resolved", data });
},
(error) => {
console.log("Error: ", error);
dispatch({ type: "rejected", error });
}
)
.catch((error) => {
console.log("Exception: ", error);
dispatch({ type: "rejected", error: { message: error.message } });
});
}, []);
return [state, callback];
};
I have spent 3 days digging around and trying to figure out if something is wrong in the config or the way the custom hook or the component is written.

Turns out the tests were executing pretty fast ending up with race condition(s). I've added await waitForElementToBeRemoved(screen.getByText(<element you are looking for>)) and all tests are passing now.

Related

Why batchWrite only write up to 3 data? Firestore

I am using Firestore to store data for my Reactjs app. I have a function as such:
export async function batchAddProduct(data) {
const productRef = doc(collection(db, "product"));
const batch = writeBatch(db);
for (const datum of data) {
batch.set(productRef, datum);
}
return await batch
.commit()
.then(() => {
return { data: true, error: null };
})
.catch((err) => {
return { data: null, error: err };
});
}
So basically, I want to add lots of data at once. Hence, I'm using the writeBatch method. I see from an answer in SO where they use doc(collection(db, "product") to generate an empty doc first then use batch.set() to fill the doc. So I'm doing that here, and I'm passing up to 500 data at once (which is the maximum limit of a batch write), but somehow only up to 3 data is being written into the database. Why is that? Am I missing something?
Update:
According to the comment:
When I console.log(data), it basically prints out an array with 500 objects in it (which I definitely can't paste in here). But I can assure you that it is receiving the correct data.
batchAddProduct is called in a redux sagas as such:
function* BATCH_ADD_PRODUCT(input) {
yield put({
type: actions.SET_STATE,
payload: {
loadingUpdate: true,
},
});
const { data, error } = yield call(batchAddProduct, input.payload.data);
if (data) {
yield put({
type: actions.GET_PRODUK,
});
yield put({
type: actions.SET_STATE,
payload: {
loadingUpdate: false,
alert: {
type: "success",
message: "Product is added successfully.",
},
},
});
}
if (error) {
console.log(error);
yield put({
type: actions.SET_STATE,
payload: {
loadingUpdate: false,
alert: {
type: "error",
message: error.message || "Error occured.",
},
},
});
}
}
and I use this in a dispatch as such:
dispatch({
type: actions.BATCH_ADD_PRODUK,
payload: {
data: data, // WHICH CONTAINS UP TO 500 OBJECTS
},
});
I haven't tried the generator function with a batched write yet but try the following:
const myArray: any = []
const batches: WriteBatch[] = []
myArray.forEach((doc, i) => {
if (i % 500 === 0) {
batches.push(writeBatch(db))
}
const productRef = doc(collection(db, 'colName'))
const batch = batches[batches.length - 1]
batch.set(productRef, { ...data })
})
await Promise.all(batches.map((batch) => batch.commit()))
console.log('done')

Adyen UPI client integration get status

I am somehow trying to get the status (error,success) after paying through the UPI app. I already end up in the onAdditionalDetails() function but here I somehow don't have the possibility to query the status. Is there maybe something needed to get this information in the state object?
async initAdyen_newurl() {
let config = null;
config = {
...this.config.adyenConfig,
onPaymentCompleted: (result, component) => {
console.info("onPaymentCompleted");
console.info(result, component);
},
onError: (error, component) => {
console.error("onError");
console.error(error.name, error.message, error.stack, component);
},
onAdditionalDetails: (state, component) => {
const actionUrl = "hardcoded for the moment"
const obj = {
paymentMethodType: component.props.paymentMethodType,
url: actionUrl,
method: "post",
type: "redirect",
paymentData: component.props.paymentData
}
component.props.createFromAction(obj, {}).mount("#id");
},
};
AdyenCheckout(config)
.then((checkout) => {
// init stuff
})
.catch((error) => {
console.error(`url failure ${error.message}`);
});
},
I can also redirect to the next page using createFromAction(), but this just happens in both Success and Error. However, this should only happen in Success. I hope that was somehow understandable. Many Thanks
edited: i am using version 5.23.0
The flow involves an additional step (3DS) so the onAdditionalDetails handler is invoked. From there you can add an extra call to /payments/details to fetch the payment status.
The response includes the resultCode to inform the shopper of the payment status.
Here is an example:
...
onPaymentCompleted: (result, component) => {
handleServerResponse(result, component);
},
onAdditionalDetails: async (response, _component) => {
// call server
const paymentDetailsResponse = await callServer("/api/paymentDetails", response);
// obtain payment status
const result = paymentDetailsResponse.resultCode
},
onError: (error, component) => {
console.error(error.name, error.message, error.stack, component);
}
// Calls your server endpoints
async function callServer(url, data) {
const res = await fetch(url, {
method: "POST",
body: data ? JSON.stringify(data) : "",
headers: {
"Content-Type": "application/json",
},
});
In the backend perform the paymentsDetails call to obtain the Payment status from the Adyen platform:
// Check payment result
app.post("/api/paymentDetails", async (req, res) => {
try {
const response = await checkout.paymentsDetails({
details: req.body.data.details,
paymentData: req.body.data.paymentData,
});
res.json(response);
} catch (err) {
console.error(`Error: ${err.message}, error code: ${err.errorCode}`);
res.status(err.statusCode).json(err.message);
}
});
See Confirm an additional action on your server

axios doesn't return response and error separately

I have a React component. Inside that component I have a function onFormSubmit that calls function from another component. This other function is making POST request with axios.
I would like to return if POST request is true a response into first function or error if not. What is happening now is that my 'SUCCESS RESPONSE' console.log is always triggered, even then there is an error in axios POST request. If there is an error then just 'ERROR RESPONSE' console.log should be triggered.
From first component
onFormSubmit = () => {
postJobDescriptionQuickApply(this.state, this.props.jobDescription.id)
.then((response) => {
console.log('SUCCESS RESPONSE', response)
})
.catch((error) => {
console.log('ERROR RESPONSE', error)
})
}
From second component
export const postJobDescriptionQuickApply = (easyApplyData, jobId) => apiUrl('easyApply', 'easyApply').then(url => axios
.post(url, {
applicant: {
email: easyApplyData.email,
fullName: `${easyApplyData.firstName} ${easyApplyData.lastName}`,
phoneNumber: easyApplyData.phoneNumber,
resume: easyApplyData.resume,
source: easyApplyData.source,
},
job: {
jobId,
},
})
.then((response) => {
console.log('SUCCESS', response.data.developerMessage)
return response.data.developerMessage
})
.catch((error) => {
// handle error
console.log('ERROR JOB DESCRIPTION', error.response.data.developerMessage)
return error.response.data.developerMessage
})
calling return indicates success, and the .catch function in the calling method wouldn't be triggered. Instead of returning error.response.data.developerMessage use throw instead. This will cause it to be thrown and then caught with the .catch method in the calling function.
Depending on the situation though, it's generally not advisable to catch and rethrow exceptions like that because you lose stack trace etc. You may be better off not catching the error in the lower method and just relying on the calling method to handle the error.
In the
.catch((error) => {
// handle error
console.log('ERROR JOB DESCRIPTION', error.response.data.developerMessage)
return error.response.data.developerMessage
})
replace return statement with throw error
Not use catch and catch on your second component.
To can use then and catch on your first component you need return axios, something as:
export const postJobDescriptionQuickApply = (easyApplyData, jobId, url) => axios
.post(url, {
applicant: {
email: easyApplyData.email,
...
},
job: {
jobId,
},
});
// or using apiUrl
export const postJobDescriptionQuickApply = (easyApplyData, jobId) => apiUrl('easyApply', 'easyApply')
.then(url => axios.post(url, {
applicant: {
email: easyApplyData.email,
fullName: `${easyApplyData.firstName} ${easyApplyData.lastName}`,
phoneNumber: easyApplyData.phoneNumber,
resume: easyApplyData.resume,
source: easyApplyData.source,
},
job: {
jobId,
},
});
Additionally, do not forget to validate the response status in the first component, something as:
onFormSubmit = () => {
postJobDescriptionQuickApply(this.state, this.props.jobDescription.id)
.then((response) => {
if (response.status === 200) {
console.log('SUCCESS RESPONSE', response);
}
})
.catch((error) => {
console.log('ERROR RESPONSE', error)
})
}
I hope, I could help you

Jest is green even if Expected is not equal Received

it('User is already present as a supplier', (done) => {
const store = mockStore({}, [{ type: 'get_user', data: { } }]);
return store.dispatch(userGetAction({ role: 'supplier' }, () => {})).then(() => {
try {
expect(store.getActions()[0].data.disabled).toEqual(true);
expect(store.getActions()[0].data.errormessage).toEqual('User is already present as a assitantbuyer');
} catch (err) {
console.log(err);
}
done();
}).catch(() => {
done();
});
});
Why it is passing and showing green status even it is Expected is not equal to Actual.
PASS src/actions/user-get-action-assistant-buyer.test.jsx
● Console
console.error node_modules/fbjs/lib/warning.js:33
console.log src/actions/user-get-action-assistant-buyer.test.jsx:25
{ Error: expect(received).toEqual(expected)
Expected value to equal:
"User is already present"
Received:
"User is already present"
at store.dispatch.then (/Users/prakashchandrabarnwal/Desktop/myProductInduct_CE_Admin/src/actions/user-get-action-assistant-buyer.test.jsx:23:57)
matcherResult:
{ actual: 'User is already present as a assitant buyer',
expected: 'User is already present as a assitantbuyer',
message: [Function],
name: 'toEqual',
pass: false } }
If i do not wrap expect inside try catch its silently going inside .catch()
thunk code returning UnhandledPromiseRejectionWarning:
const buyerAction = (data = {}, cb) => dispatch => axios({
method: 'POST',
url: `http://localhost:3001/api/manageUsers`,
headers: {
'x-access-token': authService.getAccessToken()
},
data
}).then(res => new Promise((resolve, reject) => {
if (res.status === 200 && res.data) {
dispatch({ type: 'buyer_created', data: res.data.message });
if (data.role === 'buyer') {
axios({
method: 'POST',
url: `http://localhost:3001/api/populateBuyerLookUp`,
headers: {
'x-access-token': authService.getAccessToken()
},
data
})
.then((response) => {
resolve(response);
}).catch((err) => {
reject(err);
});
}
cb(res.data.message);
} else {
reject(res);
}
}))
.catch(() => {
console.log('error');
});
(node:44182) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
So, your expect fails with an error, you catch this error and just log it, in other words, you mute the error. After all, you call "done" just like there is no error.
The code doing things just like you wrote: ignore and mute any errors. You have to remove all catch from your test
it('User is already present as a supplier', () => {
const store = mockStore({}, [{ type: 'get_user', data: { } }]);
return store
.dispatch(userGetAction({ role: 'supplier' }, () => {}))
.then(() => {
expect(store.getActions()[0].data.disabled).toEqual(true);
expect(store.getActions()[0].data.errormessage).toEqual('User is already present as a assitantbuyer');
});
});
You can return Promise from your test (There is no need to use done) and if promise fails, the whole test fails. that's all
UPD: regarding UnhandledPromiseRejectionWarning, I think it could be linked with your request to the "populateBuyerLookUp", this request completely out of the flow. I tried to fix it, but it difficult to understand what you want to do exactly
const buyerAction = (data = {}, cb) => dispatch => axios({
method: 'POST',
url: `http://localhost:3001/api/manageUsers`,
headers: {
'x-access-token': authService.getAccessToken()
},
data
})
.then((res) => {
dispatch({type: 'buyer_created', data: res.data.message});
let promise;
if (data.role === 'buyer') {
promise = axios({
method: 'POST',
url: `http://localhost:3001/api/populateBuyerLookUp`,
headers: {
'x-access-token': authService.getAccessToken()
},
data
});
}
return Promise.resolve(promise).then(() => res.data.message);
})
.then((message) => {
cb(message)
}).catch(()=>{console.log("error")});

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);
});
};

Categories

Resources