writing test for service calls in enzyme - react - javascript

how can i write the test cases for the below component using jest.
should i mock the import files.
// App.js
import React, {Component} from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Layout from "../Layout";
import Home from "../components/pages/Home";
import Users from "../components/pages/Users";
import UserList from "../components/pages/UserList";
import AppContext from "../components/context/App/index";
import Loader from "../components/Loader";
import Error from "../components/Error";
import config from "../config";
import http from '../lib/http';
class Routes extends Component {
state = {
displayLoader: true,
displayError: false,
errorMessage: null,
data: null
};
// Life cycle methods
componentWillMount() {
this.setState({
displayLoader: true
});
}
componentDidMount() {
this._resolveData();
}
_resolveData = async () => {
try {
const data = await http.get(config.endPoints.dataEndPoint);
// check list are available
if (data.list) {
this.setState({
data: data.list,
displayLoader: false
});
} else {
const error = {
message: data.message,
status: data.status
};
throw error;
}
} catch (error) {
this.setState({
displayLoader: false,
displayError: true,
errorMessage: error.message
});
}
};
render() {
const {
displayLoader,
displayError,
errorMessage,
data
} = this.state;
return displayLoader ? (
<Loader />
) : displayError ? (
<Error message={errorMessage} />
) : Object.keys(data).length > 0 ? (
<Router>
<AppContext.Provider value={data}>
<Layout>
<Switch>
<Route path="/sample" exact component={Home} />
<Route path="/sample/userList/" component={UserList} />
<Route path="/sample/users/" component={Users} />
</Switch>
</Layout>
</AppContext.Provider>
</Router>
) : (
<p>Please contact support team</p>
);
}
}
export default Routes;
//lib/http.js
async function get(url) {
try {
const response = await fetch(url, { method: 'get' });
const body = await response.json();
return body;
}
catch (err) {
throw err;
}
}
export default {get}
//app.test.js
import React from 'react';
import {shallow} from 'enzyme'
import App from '..';
describe('Given the Router component is rendered', () => {
test('it should render the markup', () => {
const tree = shallow(<App />);
expect(tree).toMatchSnapshot();
});
test('should get the data once service resolves', () => {
})
});
any help appreciated :)

Related

how solve this error (Failed to load resource: the server responded with a status of 404 ())

i'm using "react with axios" to get data from fake API ( https://jsonplaceholder.typicode.com)
what i want from the code: when i press on a certain user, all user's information should appear to me , while this error "Failed to load resource: the server responded with a status of 404 ()" appear to me and i don't know what i do.
.........................
App.js
.........................
import React, { Component, Fragment } from 'react'
import { BrowserRouter, Routes, Route, NavLink} from 'react-router-dom'
import './App.css';
import Home from './component/Home/Home';
import About from './component/About/About';
import UsersPage from './component/UsersPage/UsersPage';
import UserPage from './component/UserPage/UserPage'
class App extends Component {
render() {
return (
<BrowserRouter>
<Fragment>
<NavLink to='/'> Home</NavLink>
<NavLink to='/users'> Users</NavLink>
<NavLink to='/About'> About</NavLink>
<Routes>
<Route path='/' exact element={<Home />} />
<Route path='/About' exact element={<About />} />
<Route path='/users' exact element={<UsersPage />} />
<Route path='/users/:id' exact element={<UserPage />} />
</Routes>
</Fragment>
</BrowserRouter>
);
}
}
export default App;
.........................
Users-axios.js
.........................
import axios from "axios";
export async function getUsers() {
const response = await
axios.get('https://jsonplaceholder.typicode.com/users');
console.log(response);
console.log(response.data);
return response;
}
export async function getUser(id) {
const respon = await
axios.get('https://jsonplaceholder.typicode.com/users/'+id);
return respon;
}
.........................
UsersPage.js
.........................
import React, { Component } from 'react'
import {getUsers} from '../../Api/Users-axios'
import {NavLink} from 'react-router-dom'
export class UsersPage extends Component {
state = {
users: []
}
componentDidMount = () => {
getUsers().then(respo => {
console.log(respo)
this.setState({
users: respo.data
})
})
.catch(error => {
alert('error mount');
})
}
render() {
return (
<div>
<ul>
{this.state.users.map(user =>
<li key={user.id}>
{user.name}{" "}
{user.id}
<NavLink to={"/users/" + user.id}>View</NavLink>
</li>
)}
</ul>
</div>
)
}
}
export default UsersPage;
.........................
UserPage.js
.........................
import React, { Component } from 'react'
import {getUser} from '../../Api/Users-axios'
import ViewUserComp from '../ViewUserComp/ViewUserComp'
import {useParams} from 'react-router'
export class UserPage extends Component {
state = {
user:{}
}
componentDidMount = () => {
console.log(this.props)
let {id} = useParams;
console.log('my id:' );
console.log(id);
getUser(id).then(response => {
this.setState({
user: response.data
});
})
.catch(error => {
alert('error');
});
}
render() {
return (
<div>
<h2>User Page</h2>
<ViewUserComp user={this.state.user} />
</div>
)
}
}
export default UserPage;
...............................
ViewUserComp.js
...............................
import React from 'react'
function ViewUserComp(props) {
return (
<div>
<p>id: {props.user.id}</p>
<p>Name: {props.user.name}</p>
<p>Email: {props.user.email}</p>
</div>
)
}
export default ViewUserComp;
The error is clear as in the following image
You have to wrap the class component inside of withRouter since hooks won't work with class based components.
......................... UserPage.js .........................
import React, { Component } from 'react'
import {getUser} from '../../Api/Users-axios'
import ViewUserComp from '../ViewUserComp/ViewUserComp'
import {withRouter} from 'react-router'
export class UserPage extends Component {
state = {
user:{}
}
componentDidMount = () => {
console.log(this.props)
const id = this.props.match.params.id; // try this one
console.log('my id:' );
console.log(id);
getUser(id).then(response => {
this.setState({
user: response.data
});
})
.catch(error => {
alert('error');
});
}
render() {
return (
<div>
<h2>User Page</h2>
<ViewUserComp user={this.state.user} />
</div>
)
}
}
export default withRouter(UserPage); // wrap it
In hook, Use useParams(). Example like below.
import {useParams} from 'react-router-dom';
let { id } = useParams();

componentDidUpdate() is not firing when component is wrapped in HOC

I have HOC component which wraps all the page Components. The page component has pagination, when user clicks next, the route params changes and the difference between route param and state is compared in componentDidUpdate and then api is called. The code works on without wrapping HOC.
Routes
import React from 'react';
import { Redirect, Route, Switch, withRouter } from 'react-router-dom';
import hocWrapper from './hocWrapper'
import Dashboard from './components/screens/dashboard/Dashboard';
import Movies from './components/screens/movies/Movies';
const Routes = (props) => (
<Switch style={{ position: 'absolute' }}>
<Route exact path="/all/page:pageNumber" component={hocWrapper(Dashboard)} />
<Route exact path="/movies/page:pageNumber" component={Movies} />
</Switch>
);
export default withRouter(Routes);
HOC wrapper Component
import React, { useEffect } from 'react';
import { useDispatch } from "react-redux";
import { searchTextAction } from './containers/actions/userActions'
export default function (ComposedClass) {
const ClosedRouteForUser = (props) => {
const dispatch = useDispatch();
useEffect(() => {
console.log(window.location.pathname)
if (window.location.pathname !== `/search/page1` &&
window.location.pathname.includes('details') === false) {
dispatch(searchTextAction(''))
}
}, []);
return <ComposedClass {...props} />;
};
return ClosedRouteForUser;
}
Page Component
import React, { Component } from 'react'
import apiCall from '../../../services/apiCall';
import { trendingURL } from '../../../services/apiURL'
import MediaList from '../../common/MediaList'
import { withRouter } from 'react-router-dom';
class Dashboard extends Component {
state = {
dataList: [],
refresh: false,
pageNumber: this.props.match?.params && this.props.match.params.pageNumber,
}
async componentDidMount() {
try {
if (this.props.match?.params.routedFrom) {
localStorage.setItem("routedFrom", this.props.match.params.routedFrom)
}
console.log('cd mount')
window.scrollTo(0, 0)
this.setState({ refresh: true })
let data = { page: 1, media_type: "all" }
let apiData = await apiCall(trendingURL, data)
this.setState({ dataList: apiData.results, refresh: false })
} catch (error) {
console.log(error)
}
}
async componentDidUpdate(prevProps, prevState) {
if (this.props.match.params.pageNumber !== this.state.pageNumber) {
console.log('cd updates')
let data = { page: this.props.match.params.pageNumber, media_type: "all" }
let apiData = await apiCall(trendingURL, data)
this.setState({
dataList: apiData.results,
pageNumber: this.props.match.params.pageNumber,
refresh: false
})
}
}
pageNavigate = (value) => {
window.scrollTo(0, 0)
this.setState({ pageNumber: value })
this.props.history.replace({ pathname: `/all/page${value}` })
}
previous = () => {
this.pageNavigate(parseInt(this.props.match.params.pageNumber) - 1)
}
next = () => {
this.pageNavigate(parseInt(this.props.match.params.pageNumber ?
this.props.match.params.pageNumber :
1) + 1)
}
render() {
const { dataList, refresh } = this.state
return (
<MediaList
listData={dataList}
refresh={refresh}
previous={this.previous}
next={this.next}
/>
)
}
}
export default withRouter(Dashboard)

Action not triggered in React/Redux

I'm new to redux and trying to fetch content from my BackEnd API. For some reason the action I call does not reach the reducer (It's not even executed). I first thought it was because it couldn't access the store since it is has a parent component but my Provider is well configured and there is another component at the same level, and just after i started thinking it was a problem with my dispatch but honestly i don't know. I have attached the code I feel is relevant and any contributions would be highly appreciated.
actions/viewers.js
import axios from 'axios';
import { VIEWERS_LOADED, VIEWERS_ERROR } from './types';
export const loadData = async (body, http) => {
const config = {
headers: {
'Content-Type': 'application/json',
},
};
try {
const res = await axios.post(
http,
body,
config
);
return res.data;
} catch (error) {
console.log(error);
}
};
export const extractConcurrentViewers = (from, to, aggregate) => async dispatch => {
console.log("CONCURRENT VIEWERS");
const body = {
session_token: localStorage.token,
from,
to,
};
try {
let aggregateConcur = null;
const graphConccur = await loadData(body, 'http://localhost:5000/audience');
console.log('extractViews -> res_1', graphConccur);
if (aggregate !== null) {
body.aggregate = aggregate
aggregateConcur = await loadData(body, 'http://localhost:5000/audience');
}
console.log('extractaggregateViewers -> res_2', aggregateConcur);
dispatch({
type: VIEWERS_LOADED,
payload: {
graphConccur,
aggregateConcur
},
});
} catch (error) {
console.log(error);
dispatch({
type: VIEWERS_ERROR,
});
}
}
reducers/viewers.js
import {
VIEWERS_LOADED,
VIEWERS_ERROR,
} from '../actions/types';
const initialState = {
session_token: localStorage.getItem('token'),
concurrence: null,
aggConcurrence: null,
};
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case VIEWERS_LOADED:
return {
...state,
...payload,
concurrence: payload.graphConccur.audience,
aggConcurrence: payload.aggregateConcur.audience,
};
case VIEWERS_ERROR:
return {
...state,
concurrence: null,
aggConcurrence: null,
};
default:
return state;
}
}
reducer/index.js
import {combineReducers} from 'redux';
import alert from './alert';
import auth from './auth'
import profile from './profile'
import chart from './chart'
import viewers from './viewers'
export default combineReducers({
alert,
auth,
profile,
chart,
viewers
});
App.js
import React, { useEffect } from 'react';
import Navbar from './components/layout/Navbar';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Landing from './components/layout/Landing';
import Login from './components/auth/Login';
import Register from './components/auth/Register';
import Alert from './components/layout/Alert';
import Dashboard from './components/dashboard/Dashboard';
import PrivateRoute from './components/routing/PrivateRouting';
import { Provider } from 'react-redux';
import store from './store';
import { loadUser } from './actions/auth';
import setAuthToken from './utils/setAuthToken'
import './App.css';
if (localStorage.token) {
setAuthToken(localStorage.token);
}
const App = () => {
useEffect(() => {
store.dispatch(loadUser())
}, []);
return (
<Provider store={store}>
<Router>
<Navbar />
<Route exact path='/' component={Landing} />
<section className='container'>
<Alert />
<Switch>
<Route exact path='/login' component={Login} />
<Route exact path='/register' component={Register} />
<PrivateRoute exact path='/dashboard' component={Dashboard} />
</Switch>
</section>
</Router>
</Provider>
);
};
export default App;
This is where the function extractConcurrentViewers is to be called and the component supposed to use that is <Concurrent concurrence={concurrence}/> and what is really weird about is that the component just above it is implemented almost the same way but it's working.
import React, { useEffect, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Spinner from '../layout/Spinner';
import BandWidth from './BandWidth';
import Concurrent from './Concurrent';
import { extractCurrentClient } from '../../actions/profile';
import { extractchartData } from '../../actions/chart';
import { extractConcurrentViewers } from '../../actions/viewers';
const Dashboard = ({
extractCurrentClient,
extractchartData,
auth: { user },
profile: { profile, loading },
chart: { cdn, p2p, maxSum, maxCdn },
viewers: {concurrence}
}) => {
useEffect(() => {
extractCurrentClient();
extractchartData('max', 1585834831000, 1589118031000);
extractConcurrentViewers(1585834831000, 1589118031000);
}, []);
return loading && profile === null ? (
<Spinner />
) : (
<Fragment>
<h1 className='large text-primary'>Streaming</h1>
<p className='lead'>
<i className='fas fa-chart-line'></i>
Welcome {user && user.lname}
</p>
<BandWidth cdn={cdn} p2p={p2p} maxSum={maxSum} maxCdn={maxCdn} />
{/* <Concurrent concurrence={concurrence}/> */}
</Fragment>
);
};
Dashboard.propTypes = {
extractCurrentClient: PropTypes.func.isRequired,
extractchartData: PropTypes.func.isRequired,
extractConcurrentViewers: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired,
profile: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
auth: state.auth,
profile: state.profile,
chart: state.chart,
viewers: state.viewers,
});
export default connect(mapStateToProps, {
extractCurrentClient,
extractchartData,
extractConcurrentViewers
})(Dashboard);
store.js
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
You mapped extractConcurrentViewers to props in connect but did not add it to the destructured props object. Since they share the same name, that means is you're calling your action creator without it being bound to dispatch, so it will not be delivered to your reducers.
const Dashboard = ({
extractCurrentClient,
extractchartData,
auth: { user },
profile: { profile, loading },
chart: { cdn, p2p, maxSum, maxCdn },
viewers: {concurrence},
extractConcurrentViewers // <-- add this
}) => {
Personally I don't destructure my props and this is one reason. I prefer the code to be explicit about where values and functions are coming from props.extractConcurrentViewers . But that's my preference.

What is the correct way of redirecting after successful post request in React?

I'm new to React and I am setting up a small project. I am using a NodeJS server that answers to my request and I am trying to redirect the user after an successful login. I dispatch an action and update my redux store with the user information, that is working correctly. But when I try to redirect him I either get no errors and nothing happens or the URL changes but no component renders.
BTW in LoginForm.js I was trying to return a redirect after many fails by trying to add a callback with history object to my action.
So here is my code
App.js
import React, { Component } from 'react';
import LoginPage from './login/LoginPage';
import LandingPage from './landingpage/landing.page';
import ProtectedRoute from './protected/ProtectedRoute';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import PageNotFound from './common/pageNotFound';
class App extends Component {
render() {
return (
<Router >
<Switch>
<Route path="/login" component={() => <LoginPage />} />
<ProtectedRoute path="/" component={LandingPage} />
<Route component={() => <PageNotFound />} />
</Switch>
</Router>
)
}
}
export default App;
LoginPage.js
import React, { Component } from 'react'
import LoginForm from './LoginForm';
import PropTypes from 'prop-types'
import { connect } from 'react-redux';
import { login } from '../../actions/authActions';
import { withRouter } from "react-router";
class LoginPage extends Component {
render() {
const { login, userLoading, history } = this.props;
return (
<div>
<h1>Login in here</h1>
<LoginForm login={login} isLoading={userLoading} history={history} />
</div>
)
}
}
LoginPage.propTypes = {
login: PropTypes.func.isRequired
}
function mapStateToProps(state) {
return {
userLoading: state.auth.isLoading
}
}
export default connect(mapStateToProps, { login })(withRouter(LoginPage));
LoginForm.js
import React, { Component } from 'react'
import TextInput from '../common/TextInput';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
class LoginForm extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
redirect: false,
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value
})
}
handleSubmit(event) {
event.preventDefault();
this.setState({ error: null });
this.props.login(this.state);
this.setState({
redirect: true
})
}
render() {
const { isLoading, isAuth } = this.props;
const { redirect } = this.state;
console.log(redirect, isAuth)
if (redirect && isAuth) {
return <Redirect to="/" />
}
else {
return (
<form onSubmit={this.handleSubmit}>
<TextInput type="email" name="email" label="Email" onchange={this.handleChange} />
<TextInput type="password" name="password" label="Password" onchange={this.handleChange} />
{isLoading && <p>We are loggin you in</p>}
<button disabled={isLoading} type="submit">Log in</button>
</form>
)
}
}
}
const mapStateToProps = (state) => {
return {
isAuth: state.auth.isAuthenticated
}
}
LoginForm.propTypes = {
login: PropTypes.func.isRequired
}
export default connect(mapStateToProps)(LoginForm);
authActions.js
import {
LOGIN,
LOGIN_SUCCESS,
LOGIN_FAILED,
USER_LOADING,
USER_LOADED,
AUTH_ERROR,
REGISTER_SUCCESS,
REGISTER_FAIL,
} from '../constants';
export function login(payload) {
return dispatch => {
dispatch({ type: USER_LOADING })
setTimeout(function () {
return fetch('http://localhost:3000/user/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
}).then(
(res) => {
dispatch({ type: LOGIN_SUCCESS })
return res.json();
},
(err) => dispatch({ type: LOGIN_FAILED })
).then((data) => {
dispatch({
type: USER_LOADED,
payload: {
token: data.token,
userid: data.userID
}
})
});
}, 1000);
}
}
Since your LoginForm is wrapped with withRouter your can use this.props.history.pushState('/next-route')

React return json instead html and css when I press button back on mouse

I have web application (backend - node.js and frontend react) with two pages. The first page is menu and second page is orders. When I go from orders to menu and press on mouse button back, I get json from web page. Data are load correctly, but page not show html and only json. When I press back in browser, all is correct.
There is my code for Order.
import React, {Component} from 'react';
import MyNavbar from './MyNavbar';
import axios from 'axios';
import {Alert} from 'reactstrap';
import BuildOrder from './BuildOrder';
class Order extends Component {
constructor(props) {
super(props);
this.state = {
orders: [],
visible: false,
};
}
componentDidMount() {
this.getOrders();
if (this.props.location.state && this.props.location.state.alertMessage) {
this.handleUpdateStatus(this.props.location.state.alertColor, this.props.location.state.alertMessage);
}
}
handleUpdateStatus(color, message) {
this.setState({alertColor: color, alertMessage: message});
this.onShowAlert();
}
getOrders() {
const url = '/orders';
axios.get(url).then(response => {
this.setState({orders: response.data})
});
};
onShowAlert = () => {
this.setState({visible: true}, () => {
window.setTimeout(() => {
this.setState({visible: false},
this.props.history.replace({...this.props.location.pathname, state: {}}))
}, 5000)
});
};
toggle() {
this.setState({
visible: !this.state.visible
});
};
handleClickDelete = order => {
axios.delete('/order', { data: { name: order.name, build: order.build } }).then((message) => {
this.getOrders();
this.handleUpdateStatus(message.data.type, message.data.content);
}
)
.catch((err) => {
this.getOrders();
this.handleUpdateStatus('danger', err.message);
});
};
handleClickUpdate(evt, data) {
axios({
method: 'put',
url: '/orders',
headers: {},
data: data
}).then(() => {
this.handleUpdateStatus('success', 'Platba aktualizována');
}
).catch((err) => {
this.handleUpdateStatus('danger', err.message);
});
}
handleClickOrder(evt, data) {
evt.preventDefault();
axios({
method: 'post',
url: '/send/order',
headers: {},
data: data
}).then((message) => {
this.handleUpdateStatus(message.data.type, message.data.content);
}
).catch((err) => {
this.handleUpdateStatus('danger', err.message);
});
}
getOnTop(){
window.scrollTo(0, 0);
};
render() {
const orders = this.state.orders;
let ordersBuildA = [];
let ordersBuildB = [];
let handleClickDelete = this.handleClickDelete;
let handleClickUpdate = this.handleClickUpdate.bind(this);
let handleClickOrder = this.handleClickOrder.bind(this);
let getOnTop = this.getOnTop;
return (
<div>
{orders.filter(order => order.build === 'A').forEach(order => ordersBuildA.push(order))}
{orders.filter(order => order.build === 'B').forEach(order => ordersBuildB.push(order))}
<MyNavbar/>
<Alert className="text-center" color={this.state.alertColor} isOpen={this.state.visible}
toggle={this.toggle.bind(this)}>
{this.state.alertMessage}
</Alert>
<div className="container">
<BuildOrder build="A" orders={ordersBuildA} handleClickDelete={handleClickDelete.bind(this)}
handleClickUpdate={handleClickUpdate.bind(this)} handleClickOrder={handleClickOrder.bind(this)} getOnTop={getOnTop.bind(this)}/>
<BuildOrder build="B" orders={ordersBuildB} handleClickDelete={handleClickDelete.bind(this)}
handleClickUpdate={handleClickUpdate.bind(this)} handleClickOrder={handleClickOrder.bind(this)} getOnTop={getOnTop.bind(this)}/>
</div>
</div>
);
}
}
export default Order;
And index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import NotFound from './componets/NotFound';
import {BrowserRouter as Router, Route, Switch} from 'react-router-dom';
import Order from './componets/Order';
const RouterMapping = () => (
<Router>
<Switch>
<Route path='/' exact component={App}/>
<Route path='/orders' exact component={Order}/>
<Route exact component={NotFound}/>
</Switch>
</Router>
);
ReactDOM.render(
<RouterMapping/>,
document.getElementById('root')
);
module.hot.accept();
serviceWorker.unregister();
EDIT.
There is full project
https://bitbucket.org/mjedle/obedy_docker/src/master/
The problem was, that I call the same url for frontend and backend. When I added /api/orders to backend all is ok.
React - Page doesn't display when using the browser back button

Categories