I am new to redux and I want to know how to test Actions and Reducers.
I have attached a copy of both the files and would like anyone to help with a common pattern which I can use.
I am using Jest for unit testing.
URL is http://localhost:30001.
Just want to know how the testing can be done and how I can use fetch in my test cases and what can I put as my expected result.
Actions page
import {
REQUEST_CHARITIES,
REQUEST_PAYMENT,
SHOW_DONATION_AMOUNT_LIST,
UPDATE_DONATION_AMOUNT_LIST,
URL,
PAY_NOW,
UPDATE_MESSAGE,
UPDATE_TOTAL_DONATE
} from './const';
//get list of all the charities
export function requestCharitiesList() {
const request = fetch(`${URL}/charities`, {
method: 'GET'
}).then(response => response.json())
return {
type: REQUEST_CHARITIES,
payload: request
}
}
//get list of all payment amount
export function requestDonationAmount() {
return {
type: REQUEST_PAYMENT,
payload: [{
"id": 0,
"price": 10
},
{
"id": 1,
"price": 20
},
{
"id": 2,
"price": 50
},
{
"id": 3,
"price": 100
},
{
"id": 4,
"price": 500
},
]
}
}
//get the total count of charities and update the payment options list visibility
export function showDonationList() {
const paymentOptionsShow = []
const request = fetch(`${URL}/charities`, {
method: 'GET'
}).then( response => response.json())
request.then(function(result) {
if(result.length >= 1){
let arrayLength = result.length
for( var i = 0 ; i < arrayLength ; i++ ) {
paymentOptionsShow.push({active: false });
}
}
})
return {
type: SHOW_DONATION_AMOUNT_LIST,
payload: paymentOptionsShow
}
}
//to show and hide the payment options for each card
export function updateDonationList(list,id){
return {
type: UPDATE_DONATION_AMOUNT_LIST,
payload: {
"list": list,
"id": id
}
}
}
//post the current paid amount
export function payNow(id, amount, currency) {
const request = fetch(`${URL}/payments`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: `{ "charitiesId": ${id}, "amount": ${amount}, "currency": "${currency}" }`
})
.then(response => response.json())
return {
type: PAY_NOW,
payload: request
}
}
//show thank you message
export function updateMessage(message) {
return {
type: UPDATE_MESSAGE,
message: message
}
}
//get the total number of payments made
export function summaryTotal() {
const request = fetch(`${URL}/payments`,
{ method: 'GET' }).then(response => response.json())
return {
type: UPDATE_TOTAL_DONATE,
payload: request
}
}
One of my Reducers
import {
REQUEST_CHARITIES
} from '../actions/const'
export default function (state = null, action) {
switch (action.type) {
case REQUEST_CHARITIES:
return action.payload
default:
return state;
}
}
So, this is a simple example of how my comment could work. Understand?
describe('Test case', () => {
it('should return a new state', () => {
const myReducer = nameOfReducer( { state }, { action } );
expect(myReducer).toEqual({ state });
});
});
Related
I keep getting this message when I am trying to use payStack in Next.js, And I have looked for any possible means to solve this but I haven't seen the solution to it
const componentProps = {
email: userInfo.email,
amount: totalPrice * 100,
metadata: {
name: shippingAddress?.fullName,
},
publicKey,
text: "Pay Now",
onSuccess: async () => {
try {
dispatch({ type: "PAY_REQUEST" });
const { data } = await axios.put(
`/api/orders/${order._id}/pay`,
{
headers: {
authorization: `Bearer ${userInfo.token}`,
},
}
);
dispatch({ type: "PAY SUCESS", payload: data });
alert("Thanks for doing business with us! Come back soon!!");
} catch (error) {
alert(getError(error));
}
},
onClose: () => alert("Wait! Don't leave :("),
};
And the message on my console is "Cookies are not authorized, we will not send any data."
This is the endpoint
import axios from "axios";
import nc from "next-connect";
import { isAuth } from "../../../../lib/auth";
const handler = nc();
handler.use(isAuth);
handler.put(async (req, res) => {
const projectId = "projectId";
const dataset = "dataset";
const tokenWithAccess =token
await axios.post(
`https://${projectId}.api.sanity.io/v1/data/mutate/${dataset}`,
{
mutations: [
{
paths: {
id: req.query.id,
set: {
isPaid: true,
paidAt: new Date().toString(),
"paymentResult.id": req.body.id,
"paymentResult.status": req.body.email_address,
"paymentResult..email_address": req.body.id,
},
},
},
],
},
{
headers: {
"Content-type": "application/json",
Authorization: `Bearer ${tokenWithAccess}`,
},
}
);
res.send({ message: "Order Successfully" });
});
export default handler;
Here is my endpoint for the order information
I want to build "React Dropdown", which will give me options to select user while I type first letters of his name.
Users data is coming from my backend API in JSON format.
// http://localhost:5000/users
{
"users": [
{
"company_id": 1,
"name": "Sally Mae"
},
{
"company_id": 2,
"name": "Johnathan Ives"
},
{
"company_id": 3,
"name": "John Smith"
}
]
}
here's my fetch part, but I can't fetch, but my server is running, this is the code
fetchData = (inputValue, callback) => {
if (!inputValue) {
callback([]);
} else {
setTimeout(() => {
fetch("http://127.0.0.1:5000/users/" + inputValue, {
method: "GET",
})
.then((resp) => {
console.log(resp);
return resp.json()
})
.then((data) => {
const tempArray = [];
data.forEach((users) => {
console.log(tempArray);
tempArray.push({ label: `${users.name}`, value: `${users.name}`});
console.log(tempArray);
});
callback(tempArray);
})
.catch((error) => {
console.log(error, "catch the hoop")
});
});
}
}
appreciate any help !
I think what you misunderstand here is that callback, of your loadOptions prop, is where you wrap your retrieval method.
const getData = (inputValue) =>
fetch('http://127.0.0.1:5000/users/' + inputValue, {
method: 'GET',
})
.then((resp) => resp.json())
.then((data) =>
data.map((user) => ({ label: user.name, value: user.name }))
)
.catch((error) => {
console.log(error, 'catch the hoop');
});
const fetchData = (inputValue, callback) => {
if (!inputValue) {
callback(Promise.resolve([]));
} else {
callback(getData(inputValue));
}
};
I created a component that contains the "New Article" form. The user can add a new article after clicking the Save button. The click event calls this.props.fetchAddPaper(data), which saves the article to the database.
If the response is 200, I would like to display information on the page for the user that the article has been successfully saved.
If the response is 500 or 400 or 401, I would like to display information that 'something went wrong try again'. To display alerts I use react-toasts. My question is: how can I get a response from the API after clicking the Save button so that you can display a success or error alert? How do I get a response from this.props.fetchAddPaper (data) in the handleSubmit method that I am calling?
Below is the fetchAddPaper that connects to the API. How do I get a response from such a method in a component?
const apiMiddleware = ({ dispatch }) => next => action => {
next(action);
if (action.type !== 'API')
return;
let {
url, // Endpoint address, relative to $HOST/api/
method, // http method (GET, POST, DELETE etc.)
params, // URI string params
data, // Post data
onSuccess, // Function accepting response. If redux action is returned, it will be dispatched
onFinish, // Function run on either success or error
onError, // Function accepting error
onValidationError, // Function accepting response with validation error
text, // Loading text. If not provided there will be no overlay while loading data
successText // Success text, shown on green bar. If not provided it won't be shown
} = action.payload;
// Allow for onSuccess, onFinish and onError to be either redux (and thunk) actions or normal functions
const conditionalDispatch = (action) =>
action && _.isFunction(action) ? dispatch(action) : action;
const request = {
headers: {
'Accept': 'application/json'
},
url: `${host}/api/${url}`,
method,
timeout: 180000
};
if (params) {
params = { ...params };
for (let prop in params) {
if (Array.isArray(params[prop])) {
const arrayData = arrayToGetParameters(params[prop], prop);
delete params[prop];
Object.assign(params, arrayData);
}
}
}
if (data) {
if (method.toUpperCase() === "GET" || method.toUpperCase() === "DELETE") {
throw new Error("Can't add request data to get or delete method");
}
request.headers['Content-Type'] = 'application/json;text/plain;text/json';
}
request.data = data;
request.params = params;
text && dispatch(onLoadingStart(text));
let notificationId = shortId.generate();
axios.request(request)
.then((response) => {
text && dispatch(onLoadingEnd());
onSuccess && dispatch(onSuccess(response.data));
onFinish && conditionalDispatch(onFinish);
if (successText) {
dispatch(onAddFlashMessage({type: 'success', text: successText, id: notificationId}));
setTimeout(() => {
dispatch(onDeleteFlashMessage(notificationId));
}, 5000);
}
})
.catch((error) => {
onFinish && conditionalDispatch(onFinish);
// onError && conditionalDispatch(onError(error));
onError && dispatch(onError(error));
dispatch(onLoadingEnd());
if (error.response && error.response.status === 401) {
//dispatch(onLogOut()); todo: wylogowanie
return;
}
if (error.response && error.response.status === 422 && onValidationError) {
conditionalDispatch(onValidationError(error));
}
else {
dispatch(onAddFlashMessage({...httpReqErrorHandler(error), id: notificationId}));
}
setTimeout(() => {
dispatch(onDeleteFlashMessage(notificationId));
}, 5000);
});
};
export const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.ON_FETCH_ADD_PAPER:
return {
...state,
paper: action.response
};
default:
return state;
}
const onFetchAddPaper = (response) => ({ type: actionTypes.ON_FETCH_ADD_PAPER, response });
export const fetchAddPaper = (data) => {
return (dispatch) => {
dispatch({
type: 'API',
payload: {
url: 'Papers/addPaper',
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
'Accept': 'application/json',
},
data: data,
onSuccess: (response) => onFetchAddPaper(response),
onError: (error) => onFetchAddPaper(error)
}
});
};
};
handleSubmit(e) {
e.preventDefault();
let data = {
title: this.state.title,
header: this.state.header
}
this.props.fetchAddPaper(data);
console.log(this.props.paper);
//when the user first clicks the save button, the response is empty, but the second time the response has a value 200
}
function mapStateToProps(state) {
return {
paper: state.paper.paper
}
};
function mapDispatchToProps(dispatch) {
return {
fetchAddPaper: data => dispatch(fetchAddPaper(data))
}
}
//initialstore.jsx
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import apiMiddleware from './ApiMiddleware';
import rootReducers from '../RootReducers';
export default function initStore() {
const store = createStore(
rootReducers,
compose(
applyMiddleware(thunk, consoleMessages, apiMiddleware),
window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : f => f
)
);
if (module.hot) {
module.hot.accept('../RootReducers', () => {
const nextRootReducer = require('../RootReducers').default;
store.replaceReducer(nextRootReducer);
});
}
return store;
}
You can return a promise from your fetchAddPaper action
Something like this:
export const fetchAddPaper = (data) => {
return (dispatch) => {
return new Promise((resolve,reject) => {
dispatch({
type: 'API',
payload: {
url: 'Papers/addPaper',
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
'Accept': 'application/json',
},
data: data,
onSuccess: (response) => {
onFetchAddPaper(response);
resolve(response); //return your promise on success
},
onError: (error) => {
onFetchAddPaper(error);
reject(error); //return your promise on failure
}
}
});
})
};
};
So, whenever your action executes, it'll be a promise which you can then evaluate like -
this.props.fetchAddPaper(data).then(response => {..do something})
I have a reactjs app that should be returning data from a WepAPI. The dispatch I call on a function seems to be giving me this error: TypeError: Cannot read property 'then' of undefined
I have used other functions through dispatch and it worked fine but this one still sticks out.
The intended result is for the data to get back to the initial dispatch. At the moment the data comes through but is stuck when returning to the initial call.
import React from 'react';
import { connect } from 'react-redux';
import { jobActions } from '../../actions/job.actions';
import Popup from 'reactjs-popup'
import JwPagination from 'jw-react-pagination';
class LoadTable extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
pagination: [],
Search: "Search",
sort: {
column: null,
direction: 'desc',
},
}
this.clearSearch = this.clearSearch.bind(this);
this.doSearch = this.doSearch.bind(this);
this.doSort = this.doSort.bind(this);
this.runLog = this.runLog.bind(this);
this.openRunLog = this.openRunLog.bind(this);
this.onChangePage = this.onChangePage.bind(this);
}
componentDidMount() {
this.props.getJobs()
.then((res) => {
this.setState({
data: res.results.response || []
})
});
}
clearSearch() {
this.props.getJobs()
.then((res) => {
this.setState({
data: res.results.response || [], Search: "Search",
sort: {
column: null,
direction: 'desc',
}
})
});
}
doSearch(e) {
const { name, value } = e.target;
this.setState({ [name]: value });
this.props.doSearch(value)<----Initial Call
.then((res) => {
this.setState({
data: res.results.response || [],
sort: {
column: null,
direction: 'desc',
}
})
});
}
render() {
return (
use data
)}
const mapDispatchToProps = dispatch => ({
getJobs: () => dispatch(jobActions.getJobs()),
doSearch(value) {
dispatch(jobActions.doSearch(value));<----dispatch
},
});
export default connect(mapStateToProps, mapDispatchToProps)(LoadTable);
==========================================
Action being called:
function doSearch(value) {
return (dispatch) => {
dispatch({ type: jobConstants.JOB_REQUEST });
return jobService.doSearch(value)
.then(
results => {
dispatch({ type: jobConstants.JOB_SUCCESS, user });
//Ran console logs and seen the results here
return { results };
},
error => {
dispatch({ type: jobConstants.JOB_FAILURE, error });
}
);
}
}
=========================
Services
function doSearch(SearchValue) {
const requestOptions = {
method: 'POST',
headers: new Headers({
'Content-Type': 'application/json; charset=utf-8'
}),
body: JSON.stringify({SearchValue})
};
const requestPath = 'http://localhost:53986/api/jobs/postsearch';
return fetch(requestPath, requestOptions)
.then(handleResponseToJson)
.then(response => {
if (response) {
return { response };
}
}).catch(function (error) {
return Promise.reject(error);
});
}
You need an async function for your service, which returns a promise. Like this
async function doSearch(val) {
const requestOptions = {
method: 'POST',
headers: new Headers({
'Content-Type': 'application/json; charset=utf-8'
}),
body: JSON.stringify({SearchValue})
};
const requestPath = 'http://localhost:53986/api/jobs/postsearch';
const data = fetch(requestPath, requestOptions);
const jsonData = await data.json();
return jsonData;
}
Then you can call like so:
doSearch(val).then() // and so on...
This is the pattern your looking for in this case.
I am trying to test my async actions at redux but I am not getting it.
I am using nock and axios, so I am trying to receive a responde data from axios get to test my actions:
describe('Async Actions', () => {
afterEach(() => {
nock.cleanAll();
});
it('should load transmissors', (done) => {
const localStorage = {
token: 'a9sd8f9asdfiasdf'
};
nock('https://tenant.contactto.care')
.get('/api/clients/1/transmissors/', {
reqheaders: { 'Authorization': "Token " + localStorage.token }
})
.reply(200, { data: [
{
"id": 12,
"zone": "013",
"client": 1,
"description": "pingente",
"identifier": "",
"general_info": ""
},
{
"id": 15,
"zone": "034",
"client": 1,
"description": "colar",
"identifier": "",
"general_info": ""
}
]});
axios.get(`/api/clients/1/transmissors/`, {
headers: { 'Authorization': "Token " + localStorage.token },
}).then(transmissors => {
console.log(transmissors);
}).catch(error => {
throw(error);
})
done();
});
});
and here is my action:
export function loadTransmissors(clientId){
return function(dispatch){
axios.get(`/api/clients/${clientId}/transmissors/`, {
headers: { 'Authorization': "Token " + localStorage.token },
}).then(transmissors => {
dispatch(loadTransmissorsSuccess(transmissors.data, clientId));
}).catch(error => {
throw(error);
})
}
}
But I receiving this error at console.log:
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 3): SyntaxError
I found this answer from Dan Abramov:
How to unit test async Redux actions to mock ajax response
https://github.com/reactjs/redux/issues/1716
Does anyone know how to make a test with redux-thunk.withExtraArgument?
Thanks in advance.
I solved my problem injecting axios via argument at redux thunk
https://github.com/gaearon/redux-thunk#injecting-a-custom-argument
So I changed my redux thunk at my store:
applyMiddleware(thunk)
for
applyMiddleware(thunk.withExtraArgument({ axios }))
So I updated my async return functions at actions
From:
return (dispatch) => {
...
}
To:
return (dispatch, getState, { axios }) => {
...
}
at my actions.test I mocked an api with promises:
const axios = {
get: (url,params) => Promise.resolve({data: transmissors})
}
injected at redux thunk:
const middleware = [thunk.withExtraArgument({axios})];
const mockStore = configureMockStore(middleware);
function asyncActions () {
return dispatch => {
return Promise.resolve()
.then(() => dispatch(transmissorsActions.loadTransmissors(1)))
}
}
and I used the function asyncActions to test my actions at my store:
it('should load transmissors', (done) => {
const expectedAction = { type: types.LOAD_TRANSMISSORS_SUCCESS, transmissors, clientId};
const store = mockStore({transmissors: [], expectedAction});
store.dispatch(asyncActions()).then(() => {
const action = store.getActions();
expect(action[0].type).equal(types.LOAD_TRANSMISSORS_SUCCESS);
expect(action[0].transmissors).eql(transmissors);
expect(action[0].clientId).equal(1);
});
done();
});
You can have more info about redux-mock-store with this sample:
https://github.com/arnaudbenard/redux-mock-store/blob/master/test/index.js