Cypress: custom assertion - integration-testing

I'm trying to write a custom assertion in Cypress. I'm testing my API routes using cy.request and found myself duplicating a lot of tests.
context('POST', () => {
it('returns a 405 with an error message', () => {
cy.request({
method: 'POST',
url: getCurrentUserRoute,
failOnStatusCode: false,
}).then(response => {
expect(response.status).to.equal(405);
const actual = response.body;
const expected = {
message: 'This endpoint only supports the GET method.',
};
expect(actual).to.deep.equal(expected);
});
});
});
Rinse and repeat for every route and their forbidden methods.
So I wanted to abstract that logic away into a custom Cypress command that acts as a custom assertion.
import { map, toLower, without } from 'ramda';
const asyncForEach = async <T>(
array: T[],
callback: (element: T, index: number, array: T[]) => void | Promise<void>,
) => {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
};
const httpMethods = ['get', 'post', 'put', 'delete'];
Cypress.Commands.add(
'validateMethods',
(allowedMethods: string[], route: string) => {
const forbiddenMethods = without(map(toLower, allowedMethods), httpMethods);
return cy.wrap(
asyncForEach<string>(forbiddenMethods, async method => {
context(method, () => {
it('returns a 405 with an error message', () => {
return cy
.request({
method: 'POST',
url: route,
failOnStatusCode: false,
})
.then(response => {
const actual = response.status;
const expected = 405;
expect(actual).to.deep.equal(expected);
});
});
});
}),
);
},
);
The problem is, when I run this command, Cypress complains:
The following error originated from your test code, not from Cypress.
> Cannot call cy.validateMethods() outside a running test.
This usually happens when you accidentally write commands outside an it(...) test.
If that is the case, just move these commands inside an it(...) test.
Check your test file for errors.
When Cypress detects uncaught errors originating from your test code it will automatically fail the current test.
Cypress could not associate this error to any specific test.
We dynamically generated a new test to display this failure.
Check your console for the stack trace or click this message to see where it originated from.
How would I approach this? Should I don't care about abstracting this logic away? Or is there a way to create a helper function that does this for you in Cypress?

You can use cypress-each to iterate over the the routes and their forbidden methods. Each test will run regardless any previous test failures.
const routesAndForbiddenMethods = [
{ route: '/route1', forbiddenMethod: 'POST', supportedMethod: 'GET' },
{ route: '/route1', forbiddenMethod: 'PUT', supportedMethod: 'GET' },
{ route: '/route3', forbiddenMethod: 'DELETE', supportedMethod: 'POST' }
]
it.each(
routesAndForbiddenMethods,
(scenario) => `${scenario.forbiddenMethod} to ${scenario.route} returns a 405 with an error message`,
(scenario) => {
// your test code
cy.request({
method: scenario.forbiddenMethod,
url: scenario.route,
failOnStatusCode: false,
}).then(response => {
expect(response.status).to.equal(405);
const actual = response.body;
const expected = {
message: `This endpoint only supports the ${supportedMethod} method.`,
};
expect(actual).to.deep.equal(expected);
})
}
)

Related

Cypress API testing. Can not find property

I am developing Cypress tests for my API.
The response from my API in Postman is below:
{"infected" : false}
And my Cypress test is below:
describe("Testing the result after scanning file", () => {
it("Scan file", function () {
//Declarations
const fileName = 'example.json';
cy.fixture(fileName, 'binary')
.then((file) => Cypress.Blob.binaryStringToBlob(file))
.then((blob) => {
const formData = new FormData();
formData.append("file", blob, fileName);
cy.request({
method: 'POST',
headers: {
'content-type': 'multipart/form-data'
},
body: formData,
url: '/scan'
}).then(response => {
console.log('the response is: ', response.body)
expect(response.body).to.have.property('infected').and.eq(false);
});
})
});
});
In my browser, the Cypress test fails with the message:
assert expected {} to have property infected
I really have already broken my brain with this issue and still have no clue how to tackle it. Can anybody give me an idea what is going wrong?
Try converting the response to json, you may be seeing a string version of the data.
Postman output will not be helpful, it could be converting automatically in the background.
cy.request({
...
})
.then(response => response.json())
// OR
// .then(response => response.body.json())
.then(data => {
console.log('the data is: ', data) // better debug tool than Postman
expect(data).to.have.property('infected').and.eq(false);
});

Mock function in Firebase local emulator

Such as described here, I'm using local emulator (on-line) to make tests im my cloud functions.
Index.js:
var status = 200;
exports.saveAndSendMail = functions.https.onCall( async (req, res) => {
try{
let jsons = req.body;
await saveInfirestore(jsons);
await sendMail("Data saved", jsons);
} finally {
closeConnection(res, status);
}
async function saveInfirestore(json) {
//execute business logic and save in firestore (irrelevant for this question)
}
function closeConnection (res, status){
res.sendStatus(status);
res.end();
}
async function sendMail(title, message) {
try {
AWS.config.loadFromPath('./config_mail.json');
// Create sendEmail params
var params = {
Destination: {
ToAddresses: [
'mymail#gmail.com'
]
},
Message: { /* required */
Body: { /* required */
Html: {
Charset: "UTF-8",
Data: JSON.stringfy(message);
}
},
Subject: {
Charset: 'UTF-8',
Data: title
}
},
Source: '"Origin" <origin#gmail.com>',
ReplyToAddresses: [
'origin#gmail.com'
]
};
// Create the promise and SES service object
var sendPromise = new AWS.SES({apiVersion: '2022-17-01'}).sendEmail(params).promise();
}
catch(e){
throw e;
}
// Handle promise's fulfilled/rejected states
sendPromise.then(
function(data) {
console.log(data.MessageId);
}).catch(
function(err) {
console.error(err, err.stack);
});
}
index.test.js
const { expect } = require("chai");
const admin = require("firebase-admin");
const test = require("firebase-functions-test")({
projectId: process.env.GCLOUD_PROJECT,
});
const myFunctions = require("../index");
describe("Unit tests", () => {
after(() => {
test.cleanup();
});
it("test if save is correct", async () => {
const wrapped = test.wrap(myFunctions.saveAndSendMail);
const req = {
body: [{
value: 5,
name: 'mario'
}]
};
const result = await wrapped(req);
let snap = await db.collection("collection_data").get();
expect(snap.size).to.eq(1);
snap.forEach(doc => {
let data = doc.data();
expect(data.value).to.eql(5);
expect(data.name).to.eql('mario');
});
});
I execute it with: firebase emulators:exec "npm run test"
I have 2 problems.
1 - When execute, I receive the error TypeError: res.sendStatus is not a function. If I comment closeConnection call in block finally (index.js), this code run perfectly and all tests and "expect" run with success. But, this correct way is mock this method or mock 'res' calls. I tried mock with something like this:
const res = {
sendStatus: (status) => {
},
end: () => {
}
}
const result = await wrapped(req, res);
But, I receive this error:
Error: Options object {} has invalid key "sendStatus"
at /home/linuxuser/my-project/firebase/functions/myfolder/node_modules/firebase-functions-test/lib/main.js:99:19
at Array.forEach (<anonymous>)
at _checkOptionValidity (node_modules/firebase-functions-test/lib/main.js:97:26)
at wrapped (node_modules/firebase-functions-test/lib/main.js:57:13)
at Context.<anonymous> (test/index.test.js:50:26)
at processImmediate (node:internal/timers:464:21)
Problem 2:
I'm not wish receive an e-mail each time that tests executes. How I mock sendMail function?
Something very important to point out is that you are currently trying to use a Firebase callable function, as shown by the function heading functions.https.onCall(() => {});. Since you want to work with requests and response codes, the correct type of function to use is an HTTP function. You would only need to change the heading in your index.js:
exports.saveAndSendMail = functions.https.onRequest(async (req, res) => {
// function body
});
Now, your first problem can then be solved by correctly mocking the res object that is passed to the function (inside index.test.js). When testing HTTP functions, you must not use test.wrap() when calling the function, nor expect the result as you were doing with const result = await wrapped(req); This is since Wrap being only supported for testing onCall functions. You can see another snippet of how to call an HTTP function for testing in the documentation.
it("test if save is correct", async () => {
const req = {
body: [{
value: 5,
name: 'mario'
}]
};
// mocking the response object that is returned from the function:
const res = {
sendStatus: (code) => {
expect(code).to.eql(200); // asserting that we get 200 back as the response code
},
end: () => {
}
};
const result = await myFunctions.saveAndSendMail(req, res); // mocking a call to an HTTP function, without test.wrap()
// rest of the function…
For your second problem, I haven’t used AWS SES before, but it seems this library offers ways to mock the functions so that you won’t have to actually send emails during your tests.

Api middleware with redux-observable

I'm refactoring my react/redux app to use redux-observable instead of redux-thunk. Using thunk, I have an api middleware set up to listen for any actions with a CALL_API key and do some manipulation of the data, prepare headers, prepare full url, perform an api call using axios, and also do some additional action dispatches related to an api call.
Importantly, the api middleware dispatches a REQUEST_START action which gives the request an id and sets its status to pending in the network part of my state. When the promise from axios resolves or rejects, the middleware dispatches a REQUEST_END action, updating the state so that the current request is set to resolved or rejected. Then the response is returned to the calling action creator that initially dispatched the CALL_API action.
I have not been able to figure out how to do this with redux-observable. The part about the api middleware described above that I want to replicate is the REQUEST_START and REQUEST_END action dispatches. It's very convenient to have a centralized place where all api call related stuff is handled. I know I can effectively dispatch the REQUEST_START and REQUEST_END actions in each of my epics that does an api call, but I don't want to have to repeat the same code in many places.
I managed to partially solve this by creating an apiCallEpic which listens for actions with type CALL_API and does the above setup for api calls. However, an issue (or rather, something I don't like) is that the epic that initiates the api call (e.g. getCurrentUserEpic) essentially gives up control to apiCallEpic.
So, for example, when the api call succeeds and has a response, I may want to format that response data in some way before dispatching an action to be handled by my reducer. That is, getCurrentUserEpic should do some formatting of data returned from api call before sending to reducer. I was able to achieve something close to this by passing a payloadHandler callback function defined in getCurrentUserEpic that the apiCallEpic can call if/when it gets a successful response. However, I don't like this callback architecture and it seems like there's got to be a better way.
Here is some code that demonstrates my use of api middleware using thunk.
import axios from 'axios';
// actionCreators.js
// action types
const CALL_API = "CALL_API";
const FETCH_CURRENT_USER = "FETCH_CURRENT_USER";
const RECEIVE_CURRENT_USER = "RECEIVE_CURRENT_USER";
// action creators for request start and end
export const reqStart = (params = {}) => (dispatch) => {
const reduxAction = {
type: REQ_START,
status: 'pending',
statusCode: null,
requestId: params.requestId,
}
dispatch(reduxAction);
}
export const reqEnd = (params = {}) => (dispatch) => {
const {
requestId,
response = null,
error = null,
} = params;
let reduxAction = {}
if (response) {
reduxAction = {
type: REQ_END,
status: 'success',
statusCode: response.status,
requestId,
}
}
else if (error) {
if (error.response) {
reduxAction = {
type: REQ_END,
status: 'failed',
statusCode: error.response.status,
requestId,
}
}
else {
reduxAction = {
type: REQ_END,
status: 'failed',
statusCode: 500,
requestId,
}
}
}
dispatch(reduxAction);
}
// some api call to fetch data
export const fetchCurrentUser = (params = {}) => (dispatch) => {
const config = {
url: '/current_user',
method: 'get',
}
const apiCall = {
[CALL_API]: {
config,
requestId: FETCH_CURRENT_USER,
}
}
return dispatch(apiCall)
.then(response => {
dispatch({
type: RECEIVE_CURRENT_USER,
payload: {response},
})
return Promise.resolve({response});
})
.catch(error => {
return Promise.reject({error});
})
}
// apiMiddleware.js
// api endpoint
const API_ENTRY = "https://my-api.com";
// utility functions for request preparation
export const makeFullUrl = (params) => {
// ...prepend endpoint url with API_ENTRY constant
return fullUrl
}
export const makeHeaders = (params) => {
// ...add auth token to headers, etc.
return headers;
}
export default store => next => action => {
const call = action[CALL_API];
if (call === undefined) {
return next(action);
}
const requestId = call.requestId;
store.dispatch(reqStart({requestId}));
const config = {
...call.config,
url: makeFullUrl(call.config),
headers: makeHeaders(call.config);
}
return axios(config)
.then(response => {
store.dispatch(reqEnd({
response,
requestId,
}))
return Promise.resolve(response);
})
.catch(error => {
store.dispatch(reqEnd({
error,
requestId,
}))
return Promise.reject(error);
})
}
// reducers.js
// Not included, but you can imagine reducers handle the
// above defined action types and update the state
// accordingly. Most usefully, components can always
// subscribe to specific api calls and check the request
// status. Showing loading indicators is one
// use case.
Here's the code I've implemented to accomplish a similar thing with redux-observable.
export const fetchCurrentUserEpic = (action$, state$) => {
const requestType = FETCH_CURRENT_USER;
const successType = RECEIVE_CURRENT_USER;
const requestConfig = {
url: "/current_user",
method: "get",
}
const payload = {requestConfig, requestType, successType};
const payloadNormalizer = ({response}) => {
return {currentUser: response.data.data};
}
return action$.ofType(FETCH_CURRENT_USER).pipe(
switchMap((action) => of({
type: CALL_API,
payload: {...payload, requestId: action.requestId, shouldFail: action.shouldFail, payloadNormalizer},
})),
)
}
export const apiEpic = (action$, state$) => {
return action$.ofType(CALL_API).pipe(
mergeMap((action) => (
concat(
of({type: REQ_START, payload: {requestId: action.payload.requestId, requestType: action.payload.requestType}}),
from(callApi(action.payload.requestConfig, action.payload.shouldFail)).pipe(
map(response => {
return {
type: action.payload.successType,
payload: action.payload.payloadNormalizer({response})
}
}),
map(() => {
return {
type: REQ_END,
payload: {status: 'success', requestId: action.payload.requestId, requestType: action.payload.requestType},
}
})
)
)
).pipe(
catchError(error => {
console.log('error', error);
return of({type: REQ_END, payload: {status: 'failed', requestId: action.payload.requestId, requestType: action.payload.requestType}, error});
})
)
)
)
}
Any comments or suggestions are appreciated!
I've found redux-fetch-epic-builder (A lib for building "fetch actions" and generic epics handled by redux-observable) to be similar to what you are trying to achieve here (beware it uses rxjs 5, this guide to rescue). It uses fetch, not axios, but it's easy to replace that. Plus it has transformers for successful/failed actions.
The library is a bit old, but the base idea to overcome boilerplate code is still valid: Generic epic-builder to fetch data with calls to API(s).
I am a novice in React / Redux / RxJS, but the only problem I see with the redux-fetch-epic-builder is the way to configure the client (in axios terms). That is, I am not fully satisfied with (due to it being not FSA or RSAA):
//action creators
const getComments = (id, page = 1) => ({
type: GET_COMMENTS,
host: 'http://myblog.com',
path: `/posts/${id}/comments`,
query: {
page,
},
})
// ...
const epics = [
buildEpic(GET_COMMENTS),
]
but this may still be an elegant way. And the license allow to develop the library further. I have not converted the example from the library documentation to your user-related example, but with react-observable there is certainly no need to introduce a separate "api middleware". (Also, I like /SUBACTION better than _SUBACTION, but it's trivial to change.)

Cannot proper handle error in ajax from rxjs

I write application in React an Redux and I have a case that I have to send request to some resource when specific action was called and response of type 'text/html' write to state. This resource can return status 200 or 404, and I cannot write correct test for case with response 404.
To run tests I use jest library.
Action.js:
export const actions = {
GET_RESOURCE_SUCCESS: 'GET_RESOURCE_SUCCESS',
GET_RESOURCE_FAILURE: 'GET_RESOURCE_FAILURE'
};
export const getResourceSuccess = (response) => ({
type: actions.GET_RESOURCE_SUCCESS,
payload: response
});
export const getResourceFailure = () => ({
type: actions.GET_RESOURCE_FAILURE
});
Reducer.js:
import { handleActions } from 'redux-actions';
import { actions } from './Action';
const initialState = {
content: ''
};
export const getResourceReducer = handleActions(
{
[actions.GET_RESOURCE_SUCCESS]: (state, action) => ({ ...state, content: action.payload })
},
{
[actions.GET_RESOURCE_FAILURE]: () => ({ initialState })
},
initialState
);
In brief: when resource returns status 200 and content exists I want to overwrite content from initialState and call action GET_RESOURCE_SUCCESS, when resource returns status 404 and content doesn't exist I want not to overwrite the content and call action GET_RESOURCE_FAILURE.
GetResourceEpic.js:
import { ajax } from 'rxjs/observable/dom/ajax';
import { combineEpics } from 'redux-observable';
import { Observable } from 'rxjs';
import { getResourceSuccess, getResourceFailure } from '../Action';
const specificActionTypes = [
'SPECIFIC_ACTION_ONE',
'SPECIFIC_ACTION_TWO'
];
const getResource = () => ajax({
method: 'GET',
url: 'http://example.com',
headers: {
Accept: 'text/html'
},
crossDomain: true,
responseType: 'text/html'
});
const getResourceEpic = (action$, store) => action$
.filter(action => specificActionTypes.includes(action.type))
.flatMap(() => getResource()
// when response has status 200 and field response call getResourceSuccess
.map(({ response }) => getResourceSuccess(response))
// when response has status 404 and doesn't have field response call getResourceFailure
.catch(() => {
// helper statement to show in browser that the .catch() was called
console.log('Error');
return getResourceFailure();
})
);
export default combineEpics(
getResourceEpic
);
And it works in generally, but I get two errors:
first:
Uncaught TypeError: You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
at Object.subscribeToResult (subscribeToResult.js:73)
at CatchSubscriber../node_modules/rxjs/operator/catch.js.CatchSubscriber.error (catch.js:111)
at MapSubscriber../node_modules/rxjs/Subscriber.js.Subscriber._error (Subscriber.js:128)
at MapSubscriber../node_modules/rxjs/Subscriber.js.Subscriber.error (Subscriber.js:102)
at AjaxSubscriber../node_modules/rxjs/Subscriber.js.Subscriber._error (Subscriber.js:128)
at AjaxSubscriber../node_modules/rxjs/Subscriber.js.Subscriber.error (Subscriber.js:102)
at XMLHttpRequest.xhrReadyStateChange (AjaxObservable.js:327)
at XMLHttpRequest.d (raven.js:363)
and second:
Could not consume error: TypeError: Cannot read property 'length' of null
at getLinesAround (http://localhost:3000/static/js/bundle.js:47197:74)
at http://localhost:3000/static/js/bundle.js:47537:402
at Array.map (native)
at _callee2$ (http://localhost:3000/static/js/bundle.js:47517:54)
at tryCatch (http://localhost:3000/static/js/bundle.js:58220:40)
at Generator.invoke [as _invoke] (http://localhost:3000/static/js/bundle.js:58458:22)
at Generator.prototype.(anonymous function) [as next] (http://localhost:3000/static/js/bundle.js:58272:21)
at step (http://localhost:3000/static/js/bundle.js:47553:191)
at http://localhost:3000/static/js/bundle.js:47553:361
raven.js:51
And mentioned above problems don't allow me to write a tests, because only responses with status 200 pass the tests, other throws errors.
it('should dispatch GET_RESOURCE_SUCCESS when SPECIFIC_ACTION_ONE was dispatched', async () => {
store = mockStore();
const response = 'some content';
nock('http://example.com')
.get('/')
.reply(200, response);
const payload = { type: 'SPECIFIC_ACTION_ONE' };
// specificActionOne() produces and action of type 'SPECFIC_ACTION_ONE'
const action$ = ActionsObservable.of(specificActionOne(payload));
const resultAction = await getResourceEpic(action$, store).toPromise();
expect(resultAction)
.toEqual(getResourceSuccess(response));
});
And test above pass, but case with status equal to 404 doesn't pass:
it('should dispatch GET_RESOURCE_FAILURE when SPECIFIC_ACTION_ONE was dispatched', async () => {
store = mockStore();
nock('http://example.com')
.get('/')
.reply(404);
const payload = { type: 'SPECIFIC_ACTION_ONE' };
const action$ = ActionsObservable.of(specificActionOne(payload));
const resultAction = await getResourceEpic(action$, store).toPromise();
expect(resultAction)
.toEqual(getResourceFailure());
});
And test above doesn't pass and I get the result:
TypeError: You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
at Object.subscribeToResult (node_modules/rxjs/util/subscribeToResult.js:73:27)
at CatchSubscriber.Object.<anonymous>.CatchSubscriber.error (node_modules/rxjs/operator/catch.js:111:42)
at MapSubscriber.Object.<anonymous>.Subscriber._error (node_modules/rxjs/Subscriber.js:128:26)
at MapSubscriber.Object.<anonymous>.Subscriber.error (node_modules/rxjs/Subscriber.js:102:18)
at AjaxSubscriber.Object.<anonymous>.Subscriber._error (node_modules/rxjs/Subscriber.js:128:26)
at AjaxSubscriber.Object.<anonymous>.Subscriber.error (node_modules/rxjs/Subscriber.js:102:18)
at XMLHttpRequest.xhrReadyStateChange [as onreadystatechange] (node_modules/rxjs/observable/dom/AjaxObservable.js:327:32)
at XMLHttpRequest.callback.(anonymous function) (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:289:32)
at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:219:27)
at invokeInlineListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:166:7)
at EventTargetImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:122:7)
at EventTargetImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:87:17)
at XMLHttpRequest.dispatchEvent (node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:61:35)
at readyStateChange (node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:829:9)
at Request.properties.client.on (node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:947:7)
at Request.emit (events.js:132:15)
at IncomingMessage.<anonymous> (node_modules/request/request.js:1085:12)
at Object.onceWrapper (events.js:219:13)
at IncomingMessage.emit (events.js:132:15)
at endReadableNT (_stream_readable.js:1101:12)
at process._tickCallback (internal/process/next_tick.js:114:19)
The function passed to catch has to return an observable. You are returning an action.
Instead, you should do this:
import { of } from 'rxjs/observable/of';
...
.catch(() => {
console.log('Error');
return of(getResourceFailure());
})

Why does redux-mock-store don't show an action dispatched in catch promises?

I'm very bad when it comes to thinking of a title question, sorry for that.
My Problem:
I'm unit testing my async redux actions like it's suggested in the docs. I mock the API calls with nock and check for the dispatched actions with redux-mock-store. It works great so far, but I have one test that fails even though it clearly does work. The dispatched action neither does show up in the array returned by store.getActions() nor is the state changed in store.getState(). I'm sure that it does happen because I can see it when I test manually and observe it with Redux Dev Tools.
The only thing that is different in this action dispatch is that it is called in a promise in a catch of another promise. (I know that sounds confusing, just look at the code!)
What my code looks like:
The action:
export const login = (email, password) => {
return dispatch => {
dispatch(requestSession());
return httpPost(sessionUrl, {
session: {
email,
password
}
})
.then(data => {
dispatch(setUser(data.user));
dispatch(push('/admin'));
})
.catch(error => {
error.response.json()
.then(data => {
dispatch(setError(data.error))
})
});
};
}
This httpPost method is just a wrapper around fetch that throws if the status code is not in the 200-299 range and already parses the json to an object if it doesn't fail. I can add it here if it seems relevant, but I don't want to make this longer then it already is.
The action that doesn't show up is dispatch(setError(data.error)).
The test:
it('should create a SET_SESSION_ERROR action', () => {
nock(/example\.com/)
.post(sessionPath, {
session: {
email: fakeUser.email,
password: ''
}
})
.reply(422, {
error: "Invalid email or password"
})
const store = mockStore({
session: {
isFetching: false,
user: null,
error: null
}
});
return store.dispatch(actions.login(
fakeUser.email,
""))
.then(() => {
expect(store.getActions()).toInclude({
type: 'SET_SESSION_ERROR',
error: 'Invalid email or password'
})
})
});
Thanks for even reading.
Edit:
The setErroraction:
const setError = (error) => ({
type: 'SET_SESSION_ERROR',
error,
});
The httpPostmethod:
export const httpPost = (url, data) => (
fetch(url, {
method: 'POST',
headers: createHeaders(),
body: JSON.stringify(data),
})
.then(checkStatus)
.then(response => response.json())
);
const checkStatus = (response) => {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
};
Because of you are using nested async function in catch method - you need to return the promise:
.catch(error => {
return error.response.json()
.then(data => {
dispatch(setError(data.error))
})
});
Otherwise, dispatch will be called after your assertion.
See primitive examples:
https://jsfiddle.net/d5fynntw/ - Without returning
https://jsfiddle.net/9b1z73xs/ - With returning

Resources