How to get response of redux-saga action? - javascript

I am trying to get response of redux-saga action which is returned from service. These are some snippets of my project.
// ACTION
const actions = {
LOAD_CURRENT_ACCOUNT: 'user/LOAD_CURRENT_ACCOUNT'
};
export const currentUser = () => ({
type: actions.LOAD_CURRENT_ACCOUNT
});
// SERVICE
export async function currentAccount() {
try {
const url = config.endpoints.user;
const res = await http.get(url);
return res.data;
} catch (err) {
if (err.response && err.response.data) {
notification.warning({
message: err.response.data.code,
description: err.response.data.message
});
}
return false;
}
}
// SAGA
export function* LOAD_CURRENT_ACCOUNT() {
const res = yield call(currentAccount);
if (res) {
yield put({
type: actions.SET_STATE,
payload: res.data
});
}
}
Right now, when I try to log the response it simply returns action type object i.e. "user/LOAD_CURRENT_ACCOUNT" instead of this output I'm expecting API response.
// abc.js
const res = await store.dispatch(currentUser());
console.log(res);
// RESULT: "user/LOAD_CURRENT_ACCOUNT"
How can I get the API response here?

Related

Javascript Generator Function Jest Testing Yielding Same Yield

I am testing a saga generator function in my code.
Here is the code:
export function* mainFunction({ payload: { authorization } }) {
try {
const response = yield call(
fetch,
`${url}/api`,
{
headers: {
Authorization: authorization ,
},
}
);
if (response.status !== 200) {
throw new Error("Failed to load");
}
const jsonResponse = yield response.json();
const data = yield call(
helperFunction1,
Array.from(jsonResponse)
);
const updatedData = yield call(helperFunction2, data);
yield put(setData({ finalData: updatedData }));
yield put(helperFunction3({ authorization }));
} catch ({ message }) {
yield showError(message);
yield put(setError(message));
}
}
Here is the test:
const payload = {
authorization: "authorization",
};
const apiCall = call(
fetch,
`${url}/api`,
{
headers: {
Authorization: authorization,
},
}
);
describe("success", () => {
const saga = mainFunction({ payload });
const data = "data";
const response = {
status: 200,
json: () => data,
};
const jsonResponse = response.json();
it("should call API", () => {
expect(saga.next().value).toEqual(apiCall);
});
it("should get response with data", () => {
expect(saga.next(response).value).toEqual(data);
});
it("should call helperFunction1", () => {
expect(saga.next(jsonResponse).value).toEqual(
call(helperFunction1, Array.from(jsonResponse))
);
});
it("should call helperFunction2", () => {
expect(saga.next(data).value).toEqual(call(helperFunction2, data));
});
it("should dispatch success action with data", () => {
expect(saga.next(data).value).toEqual(
put(setData({ finalData: data }))
);
});
it("should dispatch status check", () => {
expect(saga.next(data).value).toEqual(
put(helperFunction3({ accessToken: "token" }))
);
});
it("should be done", () => {
expect(saga.next().done).toBe(true);
});
});
The first two tests run fine, however, the third test, to test the call of helperFunction1, each test from here is telling me that the actual yield is "fetch" and not the helperFunction1 that was expected. Basically, the "actual" result that it is yielding is the api variable declared before the test, the same expected result of the first test. The remaining tests pass, which appear to be written the same way as the third one. I am completely unsure of why the fetch function is yielded again for the third test while all the rest are correct. I thought the first saga.next() would have completed the fetch yield call. For the record, none of the helper functions themselves are generator functions.
The simple answer to this issue was that I was running the tests in isolation, therefore the yield order was not the expected order. When I ran all the tests related to this function at once, they all passed.

Promise { <pending> } - for last async function

I have two main functions. The first one gets the SOAP data from an API. The second one parses it to json and then formats it into the object I need to store. I have been logging and seeing the return values, and as of so far until now, it's been fine. However, I am calling exports.createReturnLabelfrom my test file and all I can see is Promise { <pending> } . I am returning a value at the last function. Does this code look weird to your? How can I clean it up?
const soapRequest = require('easy-soap-request');
const xmlParser = require('xml2json')
exports.createReturnLabel = async () => {
const xml = hidden
const url = 'https://ws.paketomat.at/service-1.0.4.php';
const sampleHeaders = {
'Content-Type': 'text/xml;charset=UTF-8',
};
const auth = async () => {
const {
response
} = await soapRequest({
url: url,
headers: sampleHeaders,
xml: xml,
timeout: 2000
});
const {
body,
statusCode
} = response;
return body
}
const fetchLabel = async () => {
const soapData = await auth();
try {
const json = xmlParser.toJson(soapData)
const labelData = JSON.parse(json)["SOAP-ENV:Envelope"]["SOAP-ENV:Body"]["ns1:getLabelResponse"]
return {
courier: 'dpd',
tracking_number: labelData["return"]["paknr"],
barCode: labelData["return"]["barcodecontent"],
printLabel: labelData["return"]["label"],
_ref: null
}
} catch (e) {
return (e)
}
}
return fetchLabel()
}
calling from my test file return console.log(file.createReturnLabel())
There's an async function call inside your code.
Should be: return await fetchLabel(), so that it awaits for completion before going on its merry way.

React-Redux - Parsing error: Can not use keyword 'await' outside an async function

I am working on a React application and I am using Redux to store the state. I have the following code:
menu.types.js:
export const FETCH_CATEGORY_RANKS = "FETCH_CATEGORY_RANKS";
menu.actions.js:
import { apiUrl, apiConfig } from '../../util/api';
import { FETCH_CATEGORY_RANKS } from './menu.types';
export const fetchCategoryRanks = menu => async dispatch => {
console.log("Printing menu (fetch category ranks)");
console.log(menu);
menu.map(category => {
const options = {
...apiConfig(),
method: 'PUT',
body: JSON.stringify(category)
}
const response = await fetch(`${apiUrl}/category/${category._id}`, options)
let data = await response.json()
if (response.ok) {
console.log("It got sent")
} else {
alert(data.error)
}
});
dispatch({ type: FETCH_CATEGORY_RANKS, menu });
}
menu.reducer.js:
// import INITIAL_STATE from './menu.data';
import { FETCH_CATEGORY_RANKS } from './menu.types';
const INITIAL_STATE = []
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCH_CATEGORY_RANKS:
return state;
default:
return state;
}
}
In my fetchCategoryRanks action creator, I am looping through the menu array which is given as a parameter to this function. For each category object in the menu, I am sending information about this category to a server through making a PUT request using fetch.
However, I am getting the following error:
I am not sure why I am getting this error or how to resolve it. Any insights are appreciated.
menu.map(async (category) => { <--------
const options = {
...apiConfig(),
method: 'PUT',
body: JSON.stringify(category)
}
const response = await fetch(`${apiUrl}/category/${category._id}`, options)
let data = await response.json()
if (response.ok) {
console.log("It got sent")
} else {
alert(data.error)
}
});
I think error is self explanatoy. You cannot use await keyword inside a function which is not async. async function returns a promise which allows us to use await in it. You can learn more about async functions here here
Try changing your code to this
menu.map(async (category) {
const options = {
...apiConfig(),
method: 'PUT',
body: JSON.stringify(category)
}
const response = await fetch(`${apiUrl}/category/${category._id}`, options)
let data = await response.json()
if (response.ok) {
console.log("It got sent")
} else {
alert(data.error)
}
});

Nothing executes after forEach loop in redux

My redux code looks like following
const getCategories = (res) => (dispatch,getState) => {
let categories = []
const result = res.results
console.log("here")
result.forEach((rawMaterial,index)=>{
if(!_.includes(categories,rawMaterial.category[0].name)){
categories.push(rawMaterial.category[0].name)
dispatch({type:UPDATE_CATEGORIES,categories})
}
});
console.log("here2")
}
Basically it is a function which pushes data into categories array from input parameter "res". What my problem is, "getCategories" function does not executes anything after forEach loop.
The below piece of chunk does not get executed in the above code.
console.log("here2")
Function executes nothing after forEach loop.
Full code
export const getRawMaterials = (params = {}) => (
dispatch,
getState,
{ fetch }
) => {
dispatch({ type: GET_RAW_MATERIALS_REQUEST, params });
const { token } = dispatch(getToken());
const { search, ordering } = getState().rawMaterials;
return fetch(
`/pands/raw-materials/?${qs.stringify({
search,
ordering
})}`,
{
method: "GET",
token,
success: res => {
const rawMaterials = res.results
dispatch({ type: GET_RAW_MATERIALS_SUCCESS, res });
const categories = getCategories(rawMaterials);
dispatch({type:UPDATE_CATEGORIES,categories})
},
failure: err => dispatch({ type: GET_RAW_MATERIALS_FAILURE })
}
);
};
//doubt here, function get return after forEach
const getCategories = (res) => {
let categories = []
const result = res;
return result.map(rawMaterial => {
if(_.includes(categories,rawMaterial.category[0].name)) return null;
return rawMaterial.category[0].name;
}
)
}
My code is not working from the following line:
const categories = getCategories(rawMaterials);
Neither it is giving an error. I think problem is with response coming from backend.
"res" is coming from backend. my data is in "res.results". When I type check "res.results" , it shows object but when I print it, it shows array of objects.

How to test catch statement in async await Action

Problem
I have an Action which awaits an API function. The happy path in the try is easily testable with my mocked API. However, unsure as to the best way to test and cover the .catch.
Actions
import {getRoles} from '../shared/services/api';
export const Actions = {
SET_ROLES: 'SET_ROLES'
};
export const fetchRoles = () => async dispatch => {
try {
const response = await getRoles();
const roles = response.data;
dispatch({
type: Actions.SET_ROLES,
roles
});
} catch (error) {
dispatch({
type: Actions.SET_ROLES,
roles: []
});
}
};
Actions Test
import {fetchRoles} from '../party-actions';
import rolesJson from '../../shared/services/__mocks__/roles.json';
jest.mock('../../shared/services/api');
describe('Roles Actions', () => {
it('should set roles when getRoles() res returns', async () => {
const mockDispatch = jest.fn();
await fetchRoles()(mockDispatch);
try {
expect(mockDispatch).toHaveBeenCalledWith({
type: 'SET_ROLES',
roles: rolesJson
});
} catch (e) {
// console.log('fetchRoles error: ', e)
}
});
// Here is the problem test, how do we intentionally cause
// getRoles() inside of fetchRoles() to throw an error?
it('should return empty roles if error', async () => {
const mockDispatch = jest.fn();
await fetchRoles('throwError')(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: 'SET_ROLES',
roles: []
});
});
});
Mocked API
import rolesJson from './roles.json';
export const getRoles = async test => {
let mockGetRoles;
if (test === 'throwError') {
// console.log('sad')
mockGetRoles = () => {
return Promise.reject({
roles: []
});
};
} else {
// console.log('happy')
mockGetRoles = () => {
return Promise.resolve({
roles: rolesJson
});
};
}
try {
const roles = mockGetRoles();
// console.log('api mocks roles', roles);
return roles;
} catch (err) {
return 'the error';
}
};
^ Above you can see what I tried, which did work, but it required me to change my code in a way that fit the test, but not the actual logic of the app.
For instance, for this test to pass, I have to pass in a variable through the real code (see x):
export const fetchRoles = (x) => async dispatch => {
try {
const response = await getRoles(x);
const roles = response.data;
How can we force getRoles in our mock to throw an error in our sad path, .catch test?
You can mock getRoles API on per-test basis instead:
// getRoles will be just jest.fn() stub
import {getRoles} from '../../shared/services/api';
import rolesJson from '../../shared/services/__mocks__/roles.json';
// without __mocks__/api.js it will mock each exported function as jest.fn();
jest.mock('../../shared/services/api');
it('sets something if loaded successfully', async ()=> {
getRoles.mockReturnValue(Promise.resolve(rolesJson));
dispatch(fetchRoles());
await Promise.resolve(); // so mocked API Promise could resolve
expect(someSelector(store)).toEqual(...);
});
it('sets something else on error', async () => {
getRoles.mockReturnValue(Promise.reject(someErrorObject));
dispatch(fetchRoles());
await Promise.resolve();
expect(someSelector(store)).toEqual(someErrornessState);
})
I also propose you concentrate on store state after a call not a list of actions dispatched. Why? Because actually we don't care what actions in what order has been dispatched while we get store with data expected, right?
But sure, you still could assert against dispatch calls. The main point: don't mock result returned in __mocks__ automocks but do that on peer-basis.
I resolved the test and got the line coverage for the .catch by adding a function called mockGetRolesError in the mock api file:
Thanks to #skyboyer for the idea to have a method on the mocked file.
import {getRoles} from '../shared/services/api';
export const Actions = {
SET_ROLES: 'SET_ROLES'
};
export const fetchRoles = () => async dispatch => {
try {
const response = await getRoles();
const roles = response.data;
// console.log('ACTION roles:', roles);
dispatch({
type: Actions.SET_ROLES,
roles
});
} catch (error) {
dispatch({
type: Actions.SET_ROLES,
roles: []
});
}
};
Now in the test for the sad path, I just have to call mockGetRolesError to set the internal state of the mocked api to be in a return error mode.
import {fetchRoles} from '../party-actions';
import rolesJson from '../../shared/services/__mocks__/roles.json';
import {mockGetRolesError} from '../../shared/services/api';
jest.mock('../../shared/services/api');
describe('Roles Actions', () => {
it('should set roles when getRoles() res returns', async () => {
const mockDispatch = jest.fn();
try {
await fetchRoles()(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: 'SET_ROLES',
roles: rolesJson
});
} catch (e) {
return e;
}
});
it('should return empty roles if error', async () => {
const mockDispatch = jest.fn();
mockGetRolesError();
await fetchRoles()(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: 'SET_ROLES',
roles: []
});
});
});

Categories