How to test onclick api call with react - reactjs

I have 3 react components and when the user clicks on USER_CARD in header then an api is called and the response is displayed in TwitterList component. I have no experience with unit testing, so what are the unit test needs to be done and how? I have read about enzyme and jest but not sure about the implementation.
Fews things I understand here that I need to test the click and also check if the api is responding with any data or not.
Please help me understand how to do this?
import React ,{Component}from 'react'
// Import all contianers here
import Header from './containers/header'
import TweetList from './containers/tweetlist'
// Import all services here
import Http from './services/http'
import './App.css'
class App extends Component {
constructor() {
super()
this.state = {
data: [],
isTop: true,
userName: ''
}
}
_getUserTweets = (user) => {
console.log(user)
if (user !== undefined && user !== '') {
Http.get('/' + user)
.then(response => {
if (response.data.length > 0) {
this.setState((prevState) => {
return {
...prevState,
data: response.data,
userName: user
}
})
}
})
.catch(error => {
console.log(error)
})
} else {
console.log('No user found!!')
}
}
render() {
const {data, userName} = this.state
return (
<div className="app_container">
<Header getUserTweets={this._getUserTweets} />
<TweetList data={data} user={userName} />
</div>
);
}
}
export default App;
import React, {Component} from 'react'
class TweetList extends Component {
constructor() {
super()
this.state = {
tweets: []
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.data.length > 0) {
this.setState((prevState) => {
return {
...prevState,
tweets: nextProps.data
}
})
}
}
render() {
const {tweets} = this.state
return (
<div>
{
tweets.length > 0
&&
tweets.map((currentValue, index) => {
return (
<p>{currentValue.full_text}</p>
)
})
}
</div>
)
}
}
export default TweetList
import React, {Component} from 'react'
import './style.css'
const USER_CARD = ({userName, onClickHandler}) => {
return (
<p onClick={() => onClickHandler(userName)}>{userName}</p>
)
}
class Header extends Component {
componentWillMount() {
if (process.env.REACT_APP_USER_LIST !== undefined && process.env.REACT_APP_USER_LIST.split(',').length > 0) {
this.props.getUserTweets(process.env.REACT_APP_USER_LIST.split(',')[0])
}
}
_getUserTweets = (userName) => {
this.props.getUserTweets(userName)
}
render() {
return(
<div className="header_container">
{process.env.REACT_APP_USER_LIST !== undefined
&&
process.env.REACT_APP_USER_LIST.split(',').length > 0
&&
process.env.REACT_APP_USER_LIST.split(',')
.map((currentValue, index) => {
return (
<USER_CARD userName={currentValue} key={`user-card-${index}`}
onClickHandler={this._getUserTweets} />
)
})}
</div>
)
}
}
export default Header
If the user click on the USER_CARD in Header component then we call an api to get the results.
What are the different unit testing that I can do and how to do it?

wrote this code by heart (so not tested) but should give you the idea:
unit test the onClick:
shallow the USER_CARD with enzyme like this, pass mock function, trigger click and check if the function was called with expected arguments:
const handlerMock = jest.fn()
const wrapper = shallow(<USER_CARD userName="foo" onClickHandler={handlerMock}/>)
wrapper.find('p').simulate('click') // or wrapper.find('p').prop('onClick)()
expect(handlerMock).toHaveBeenCalledTimes(1)
expect(handlerMock).toHaveBeenCalledWith("foo")
unit test the API
a) either mock the whole Http and then use mock return value, shallow your component and trigger your _getUserTweets like in 1. where I showed you how to test your onClick and then find your TweetList if data was set accordingly, here the mocking part of API:
import Http from './services/http'
jest.mock('./services/http')
const mockResponse = foobar; // response expected from your call
Http.get.mockReturnValue(({
then: (succ) => {
succ(mockResponse)
return ({
catch: jest.fn()
})
}
}))
b) dont mock Http but spyOn + mockImplementation:
const getSpy = jest.spyOn(Http, 'get').mockImplementation(() => ...) // here your mock implementation
important! restore at end of test:
getSpy.mockRestore()

Related

Unexpected token on line 17:115 reactjs

The code runs perfectly fine when i run npm start. Website does get render on browser but it still shows an error in my terminal.
Line 17:115: Parsing error: Unexpected token, expected ";"
I already did a search related to this problem. The solution, I get from most of the website is that people forget to put componentDidMount in Class component but as you can seen in my code it already present in Class componenet.
import React from 'react';
import {
List
} from './CardList';
// import {Data} from './data';
import {
SearchBox
} from './SearchBox';
import './App.css'
export class Main extends React.Component {
constructor() {
super();
this.state = {
"Data": [],
"searchfield": ''
}
}
componentDidMount() {
LINE 17---> fetch('https://jsonplaceholder.typicode.com/users').then(Response => Response.json()).then(users => this.setState({Data: users}));
}
Searchchange = (event) => {
this.setState({
searchfield: event.target.value
});
}
render() {
const filterrobot = this.state.Data.filter(Data => {
return Data.name.toLowerCase().includes(this.state.searchfield.toLowerCase())
})
if (this.state.Data.length === 0) {
return <h1 className='tc'>Loagding</h1>
} else {
return (
<div className='tc'>
<h1 className='f1'>SEARCH PICTURES></h1>
<SearchBox secondchange = {this.Searchchange}/>
<List Data={filterrobot}/>
</div>
);
}
}
}

React/TypeScript `Error: Maximum update depth exceeded.` when trying to redirect on timeout

I have a project that I'm trying to get to redirect from page 1 to 2 etc. dynamically. This has worked for me previously, but recently I'm getting this error:
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
After seeing this message this morning, and multiple SO pages saying NOT to call setState in render, I have moved my setTimeout call into componentDidMount.
So far I've tried
- calling a function that changes this.props.pageWillChange property, then in render I return a object based on that condition
- returning a object pending condition set in an inline if statement in render
- turning pageWillChange into a local prop, rather than one that is inherited by the class (I quite like this option, as the state of this will be the same for every new version of this component)
Many more things, but these felt like they would work. Anyone able to help?
import React, { Component } from "react"
import axios from "axios"
import { GridList, GridListTile } from "#material-ui/core"
import "../assets/scss/tile.scss"
import Request from "../config.json"
import DataTile from "./DiagnosticDataTile"
import { IDiagnosticResultData } from "../interfaces/IDiagnosticResultData"
import { Redirect } from "react-router"
interface IProps {
category: string
redirect: string
}
interface IPageState {
result: IDiagnosticResultData[]
pageWillChange: boolean
}
class Dashboard extends Component<IProps, IPageState> {
_isMounted = false
changeTimeInMinutes = 0.25
willRedirect: NodeJS.Timeout
constructor(props: Readonly<IProps>, state: IPageState) {
super(props)
this.state = state
console.log(window.location)
}
componentDidMount(): void {
this._isMounted = true
this.ChangePageAfter(this.changeTimeInMinutes)
axios
.get(Request.url)
.then(response => {
if (this._isMounted) {
this.setState({ result: response.data })
}
})
.catch(error => {
console.log(error)
})
}
componentWillUnmount(): void {
this._isMounted = false
clearTimeout(this.willRedirect)
}
ChangePageAfter(minutes: number): void {
setTimeout(() => {
this.setState({ pageWillChange: true })
}, minutes * 60000)
}
render() {
var data = this.state.result
//this waits for the state to be loaded
if (!data) {
return null
}
data = data.filter(x => x.categories.includes(this.props.category))
return (
<GridList
cols={this.NoOfColumns(data)}
cellHeight={this.GetCellHeight(data)}
className="tileList"
>
{this.state.pageWillChange ? <Redirect to={this.props.redirect} /> : null}
{data.map((tileObj, i) => (
<GridListTile
key={i}
className="tile"
>
<DataTile data={tileObj} />
</GridListTile>
))}
</GridList>
)
}
}
export default Dashboard
(very new with React and TypeScript, and my first SO post woo!)
Try the code below, also couple of points:
No need for _isMounted field. Code in 'componentDidMount' always runs after it's mounted.
No need to set state in constructor. Actually there is no need for constructor anymore.
I can't see much point of clearTimeout in componentWillUnmount mount. It's never asigned to timeout.
About routing. U can use 'withRouter' high order function to change route programmatically in changePageAfter method.
Hope this helps!
import axios from "axios"
import { GridList, GridListTile } from "#material-ui/core"
import "../assets/scss/tile.scss"
import Request from "../config.json"
import DataTile from "./DiagnosticDataTile"
import { IDiagnosticResultData } from "../interfaces/IDiagnosticResultData"
import { Redirect, RouteComponentProp } from "react-router"
interface PropsPassed {
category: string
redirect: string
}
type Props = PropsPassed & RouteComponentProp
interface IPageState {
result: IDiagnosticResultData[]
pageWillChange: boolean
}
class Dashboard extends Component<Props, IPageState> {
changeTimeInMinutes = 0.25
willRedirect: NodeJS.Timeout
componentDidMount(): void {
this.ChangePageAfter(this.changeTimeInMinutes)
axios
.get(Request.url)
.then(response => {
this.setState({ result: response.data })
})
.catch(error => {
console.log(error)
})
}
changePageAfter(minutes: number): void {
setTimeout(() => {
this.props.history.push({
pathname: '/somepage',
});
}, minutes * 60000)
}
render() {
var data = this.state.result
//this waits for the state to be loaded
if (!data) {
return null
}
data = data.filter(x => x.categories.includes(this.props.category))
return (
<GridList
cols={this.NoOfColumns(data)}
cellHeight={this.GetCellHeight(data)}
className="tileList"
>
{data.map((tileObj, i) => (
<GridListTile
key={i}
className="tile"
>
<DataTile data={tileObj} />
</GridListTile>
))}
</GridList>
)
}
}
export default withRouter(Dashboard)

Unit testing : Error: TypeError: Cannot read property 'displayName' of undefined

Test is updated. Able to do some other tests using the same format.
This is what I have so far
headers.test.js
import React, {Component} from 'react';
import {Headers} from './Headers';
import {configure} from 'enzyme';
import Adapter from 'enzyme-adapter-react-15';
import renderer from 'react-test-renderer';
configure({adapter: new Adapter()});
describe('Headers', () => {
let tree;
let baseProps;
let mockauthKeyValues;
let mockheaderKeyValues;
let mockaddNewKeyValue;
beforeEach(() => {
baseProps = { // assing all of the props into MOCK
authKeyValues: mockauthKeyValues,
headerKeyValues: mockheaderKeyValues,
addNewKeyValue: mockaddNewKeyValue
}
})
it('should render with all of the props', () => {
tree = renderer.create(<Headers {...baseProps} />)
let treeJson = tree.toJSON()
expect(treeJson).toMatchSnapshot();
tree.unmount()
});
});
I will be testing each props and make sure it renders without it.
I believe I am making at mistake when I mock all of this.props. Especially mockheaderKeyValues. I dont think i am setting up correctly.
This is the current code: headers.js
export class Headers extends Component {
componentDidMount () {
if (this.props.authKeyValues == null) {
this.setState({
useDefaultData: false
})
} else {
this.setState({
useDefaultData: true
})
}
}
generateKeyValues = () => {
if (this.props.headerKeyValues == null) {
return (
<KeyValue
id={shortid.generate()}
type='headers'
addNewKeyValue={this.props.addNewKeyValue}
/>
)
} else {
let defaultKeyValues = Object.keys(this.props.headerKeyValues).map ((headerKey, idx) => {
return (
<KeyValue
key={headerKey}
id={headerKey}
type={this.props.headerKeyValues[headerKey].type}
addNewKeyValue={this.props.addNewKeyValue}
defaultData={this.props.headerKeyValues[headerKey]}
/>
)
})
defaultKeyValues.push(
<KeyValue
id={shortid.generate()}
type='headers'
addNewKeyValue={this.props.addNewKeyValue}
/>
)
return defaultKeyValues
}
}
render () {
return (
<div>
{this.generateKeyValues()}
</div>
)
}
}
Found the problem -
Had to remove {} from import {Headers} since Headers import 2 more files , therefore it was not importing correctly

React authentication HoC

I have a React-Router-Redux application that I built with an expressJS server. Part of this application is authentication using JWT. Aside from protecting Routes, I am trying to create a HoC that will protect it's wrapped component by reaching out to the server and authenticating before displaying the wrapped component. Here is the HoC I have built:
withAuth.js:
import React, { Component } from 'react';
import {connect} from 'react-redux';
import * as actions from '../../store/actions';
export default function (ComposedComponent) {
class Authenticate extends Component {
componentWillMount() {
console.log('will mount');
this.props.authenticate();
}
render() {
const { loading, loaded } = this.props;
return !loading && loaded ? <ComposedComponent {...this.props} /> : null;
}
}
const mapStateToProps = state => {
return {
loading: state.auth.loading,
loaded: state.auth.loaded
};
};
const mapDispatchToProps = dispatch => {
return {
authenticate: () => dispatch(actions.authenticate())
};
};
return connect(mapStateToProps, mapDispatchToProps)(Authenticate)
}
I am using Redux Saga aswell. The authenticate action calls a saga that sets loading to true, loaded to false and reaches out to the server. When the server sends confirmation, loaded is set to true and loading is set to false, aside from a cookie and some data being saved.
It basically works, but the problem is that when I enter a route with this HoC, the authentication process is done twice (HoC's ComponentWillMount is called twice) and I cant figure out why. It happens with a wrapped component that doesnt even reach out to the server or change props on mount/update. What am I missing here?
This is one of the wrapped components that has this problem:
class SealantCustomer extends Component {
state = {
controls: {
...someControls
}
}
shouldComponentUpdate(nextProps) {
if (JSON.stringify(this.props.sealantCustomer) === JSON.stringify(nextProps.sealantCustomer)) return false;
else return true;
}
updateInput = (event, controlName) => {
let updatedControls = inputChangedHandler(event, controlName, this.state.controls);
this.setState({controls: updatedControls});
}
searchCustomer = async (event) => {
event.preventDefault();
this.props.fetchCustomer(this.state.controls.phone.value, this.state.controls.site.value, this.state.controls.name.value);
}
render () {
let sealantCustomer;
if (this.props.loading) {
sealantCustomer = <Loader />;
}
if (!this.props.loading) {
if (!this.props.sealantCustomer) this.props.error ? sealantCustomer = <h3 style={{color: 'salmon'}}>ERROR: {this.props.error}</h3> : sealantCustomer = <h3>Please search for a sealant customer</h3>
else if (this.props.sealantCustomer.length === 0) sealantCustomer = <h3>Found no sealant customers with these details!</h3>
else {
let data = [];
this.props.sealantCustomer.forEach(person => {
...filling data here
})
const columns = [{
...table columns
}]
const keysToSkip = [keys];
sealantCustomer = <ReactTable data={data} columns={columns} defaultPageSize={3} className={['-striped', '-highlight', 'tableDefaults'].join(" ")}
SubComponent={sub component} />
}
}
return (
<div className={classes.sealantCustomerPage}>
<SearchBox controls={this.state.controls} submit={this.searchCustomer} inputUpdate={this.updateInput} name="Sealant Customers" />
<div className={classes.sealantCustomer}>
{sealantCustomer}
</div>
</div>
)
}
};
const mapStateToProps = state => {
return {
loading: state.searches.loading,
error: state.searches.error,
sealantCustomer: state.searches.sealantCustomer
};
};
const mapDispatchToProps = dispatch => {
return {
fetchCustomer: (phone, site, name) => dispatch(actions.searchSealantCustomer(phone, site, name))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(SealantCustomer);

ReactJs update state from Select List

I have a react-select component with options from a axios GET, I want my Car component to display an image from a url stored in the component state when the option is selected.
I am using componentDidMount and componentDidUpdate, however, in componentDidUpdate, this.getImage(capID); keeps firing, how can I prevent this and evoke it once?
import React from "react";
import axios from "axios";
import { Panel } from "react-bootstrap";
export default class CarList extends React.Component {
constructor(props) {
super(props);
this.state = {
imageSrc: ""
};
this.getImage = this.getImage.bind(this);
}
getImage(id) {
axios
.get(`xxx${id}`)
.then(response => {
this.setState({
imageSrc: response.data.url
});
})
.catch(error => {
console.log(error);
});
}
componentDidMount() {
const {
agrNo,
balloon,
bpid,
capID,
dealer,
derivative,
id,
make,
model,
name
} = this.props.car;
this.getImage(capID);
}
componentDidUpdate() {
const {
agrNo,
balloon,
bpid,
capID,
dealer,
derivative,
id,
make,
model,
name
} = this.props.car;
this.getImage(capID);
}
render() {
let car = this.props.car;
const {
agrNo,
balloon,
bpid,
capID,
dealer,
derivative,
id,
make,
model,
name
} = this.props.car;
return (
<div className="car-details">
<Panel header={name}>
<div className="flex-container">
<div className="flex-item">
{this.state.imageSrc && (
<img
src={this.state.imageSrc}
alt={model}
className="car-details__image"
/>
)}
</div>
<div className="flex-item">
<p>{car.Plot}</p>
<div className="car-info">
<div>
<span>Genre:</span> {car.Genre}
</div>
</div>
</div>
</div>
</Panel>
</div>
);
}
}
App:
import React, { Component } from "react";
import logo from "./logo.svg";
import axios from "axios";
import { Alert } from "react-bootstrap";
import AsyncSelect from "react-select/lib/Async";
import CarList from "./CarList";
import "react-select/dist/react-select.css";
import "./App.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
car: {}
};
}
getCars(e) {
return axios
.get(`xxx${e}`)
.then(response => {
var arr = [];
if (response.data !== undefined) {
var searchResults = response.data.length;
for (var i = 0; i < searchResults; i++) {
arr.push({
label: `${response.data[i].name} - ${response.data[i].id}`,
value: response.data[i].id
});
}
}
return {
options: arr
};
})
.catch(error => {
console.log(error);
});
}
getCar(e) {
axios
.get(`xxx}`)
.then(response => {
this.setState({
car: response.data
});
})
.catch(error => {
console.log(error);
});
}
render() {
const {
car: { id }
} = this.state;
return (
<div className="container">
<AsyncSelect
name="carOwner"
value="ABC"
cacheOptions
defaultOptions
loadOptions={this.getCars}
onChange={this.getCar.bind(this)}
/>
{id ? (
<CarList car={this.state.car} />
) : (
<Alert bsStyle="info">
<p>Enter a surname above to begin...</p>
</Alert>
)}
</div>
);
}
}
export default App;
componentDidUpdate will fire whenever any prop or state for this component has changed (checkout the official docs for more info).
You're changing the state inside the getImage(id) function, and every time that happens, the componentDidUpdate function will fire in your case, which will call the getImage function again, which will then became an infinite loop.
You need to check if the capID prop has changed, in order to decide if you should make the call again or not:
componentDidUpdate(oldProps) {
const {
agrNo,
balloon,
bpid,
capID,
dealer,
derivative,
id,
make,
model,
name
} = this.props.car;
const oldCapID = oldProps.capID;
if (capID !== oldCapID) {
this.getImage(capID);
}
}

Resources