Enzyme test issues returns undefined - javascript

I'm trying to do unit test my React class component using {mount} method. When I try to access the class variable (using this keyword), running the test gives an error of undefined. Example below where this.DataSource evaluate to undefined after calling mount(<GridComponent recordArr=[1,2,3] />
export class GridComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
pageRecords: [],
totalSize: 0
}
this.options = {
page: 1,
sizePerPage: 10
}
this.DataSource = [] //All records will be stored here
}
componentDidMount() {
this.DataSource = this.props.recordArr
this.parseProps()
}
componentWillReceiveProps(nextProps) {
this.DataSource = nextProps.recordArr
this.parseProps()
}
parseProps = () => {
let pageRecords = []
if (!_.isEmpty(this.DataSource)) { //this.DataSource ==> undefined
let startIndex = (this.options.page - 1) * this.options.sizePerPage
let count = (this.options.page - 1) * this.options.sizePerPage + this.options.sizePerPage
pageRecords = _.slice(this.DataSource, startIndex, count)
}
this.setState({
...this.state,
pageRecords: pageRecords,
totalSize: this.DataSource.length
})
}
render() {
... //Render the records from this.state.pageRecords
}
}

Change this.DataSource as a component state and then use, Enzyme util setState function: in your test case.
wrapper.setState({ DataSource: 'somesource' });
For example your components's componentWillReceiveProps method will look like this:
componentWillReceiveProps(nextProps) {
this.setState({
DataSource: nextProps.recordArr,
});
this.parseProps()
}

Related

Dynamically update value based on State in React

I have a react class based component where I have defined a state as follows:
class MyReactClass extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedDataPoints: new Set()
};
}
// This method is called dynamically when there is new addition of data
storeData = (metricName, dataPoint) => {
if (this.state.selectedDataPoints.has(dataPoint)) {
this.state.selectedDataPoints.delete(dataPoint);
} else {
this.state.selectedDataPoints.add(dataPoint);
}
};
render () {
return (
<p>{this.state.selectedDataPoints}</p>
);
}
}
Note that initially, the state is an empty set, nothing is displayed.
But when the state gets populated eventually, I am facing trouble in spinning up the variable again. It is always taking as the original state which is an empty set.
If you want the component to re-render, you have to call this.setState () - function.
You can use componentshouldUpdate method to let your state reflect and should set the state using this.state({}) method.
Use this code to set state for a set:
export default class Checklist extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedDataPoints: new Set()
}
this.addItem = this.addItem.bind(this);
this.removeItem = this.removeItem.bind(this);
}
addItem(item) {
this.setState(({ selectedDataPoints }) => ({
selectedDataPoints: new Set(selectedDataPoints).add(item)
}));
}
removeItem(item) {
this.setState(({ selectedDataPoints }) => {
const newSelectedDataPoints = new Set(selectedDataPoints);
newSelectedDataPoints.delete(item);
return {
selectedDataPoints: newSelectedDataPoints
};
});
}
getItemCheckedStatus(item) {
return this.state.checkedItems.has(item);
}
// This method is called dynamically when there is new addition of data
storeData = (metricName, dataPoint) => {
if (this.state.selectedDataPoints.has(dataPoint)) {
this.state.selectedDataPoints.removeItem(dataPoint);
} else {
this.state.selectedDataPoints.addItem(dataPoint);
}
};
render () {
return (
<p>{this.state.selectedDataPoints}</p>
);
}
}

Getting a warning called Can't call setState on a component that is not yet mounted

This is my code
import React, { Component } from 'react';
class Rando extends Component {
constructor(props) {
super(props); // want to use props insode brackets if we want to use props inside the constructor
this.state = { num: 0, color: 'purple' };
this.makeTimer();
}
makeTimer() {
setInterval(() => {
let rand = Math.floor(Math.random() * this.props.maxNum);
this.setState({ num: rand });
}, 10);
}
render() {
console.log('changing');
return (
<div className=''>
<h1>{this.state.num}</h1>
</div>
);
}
}
export default Rando;
I'm getting a warning that looks like this
index.js:1 Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the Rando component.
I'm a beginner, I have no idea what's causing this. please help me. Thanks in advance
Your timer function gets executed even before the component gets mounted. Try putting the code inside componentDidMount hook. Also don't forget to clear the interval id inside componentWillUnmount.
Sandbox link for your reference: https://codesandbox.io/s/react-basic-class-component-forked-sybv0?file=/src/index.js
Modified Snippet
import React, { Component } from 'react';
class Rando extends Component {
constructor(props) {
super(props);
this.state = { num: 0, color: 'purple' };
this.timer = null;
}
componentDidMount() {
this.makeTimer();
}
componentWillUnmount() {
clearInterval(this.timer);
}
makeTimer = () => {
this.timer = setInterval(() => {
let rand = Math.floor(Math.random() * this.props.maxNum);
this.setState({ num: rand });
}, 10);
}
render() {
return (
<div>
<h1>{this.state.num}</h1>
</div>
);
}
}
export default Rando;
You should call this.makeTimer(); on componentDidMount() instead of constructor
componentDidMount(){
this.makeTimer();
}
Try this code
import React, { Component } from 'react';
class Rando extends Component {
constructor(props) {
super(props); // want to use props insode brackets if we want to use props inside the constructor
this.state = { num: 0, color: 'purple' };
this.makeTimer((data) => {
this.setState(data);
});
}
makeTimer(cb) {
setInterval(() => {
let rand = Math.floor(Math.random() * this.props.maxNum);
cb({ num: rand })
}, 10);
}
render() {
console.log('changing');
return (
<div className=''>
<h1>{this.state.num}</h1>
</div>
);
}
}
export default Rando;
Try this :
import React, { Component } from 'react';
class Rando extends Component {
constructor(props) {
super(props); // want to use props insode brackets if we want to use props inside the constructor
this.state = { num: 0, color: 'purple' };
}
makeTimer() {
setInterval(() => {
let rand = Math.floor(Math.random() * this.props.maxNum);
this.setState({ num: rand });
}, 10);
}
componentDidMount(){
this.makeTimer();
}
render() {
console.log('changing');
return (
<div className=''>
<h1>{this.state.num}</h1>
</div>
);
}
}
export default Rando;

How to test logic in ComponenWillMount using Enzyme/Jest

I am beginner in react unit testing with enzyme/jest,
I want to test my logic inside componentWillMount method.
I want to test based on my context object whether redirect happens or not based on my business logic
class ActivateSF extends Component {
constructor(props) {
super(props);
this.className = 'ActivateSF.js'
this.state = {
messages: null,
}
}
render() {
return (
<SDPActivateInterstitialUI
context={this.props.context}
messages={this.state.messages}
/>
);
}
componentWillMount() {
let context = this.props.context
if(!context.userInfo){
return this.callIdentify(context)
}
let externalLP = ExternalLandingPageUtil.getExternalLandingPageUrl(context);
if (externalLP) {
window.location.replace(`${externalLP}`);
return;
}
if (context.userInfo)
{
console.log("user identified prior to activation flow")
if (UserInfoUtil.isSubsribedUser(context))
{
window.location = '/ac'
}
else
{
this.callPaymentProcess(context)
}
}
}
You can try beforeEach to mount and in your test you call .unmount and perform your test on it.
beforeEach(() => {
const myComponent= mount(<MyComponent myprop1={...} />);
});
describe('<MyComponent/>', () => {
it('actually unmounts', () => {
...
...
myComponent.unmount();
... Do unmount tests here
});
});
Example straight from the enzyme docs: https://airbnb.io/enzyme/docs/api/ShallowWrapper/unmount.html
import PropTypes from 'prop-types';
import sinon from 'sinon';
const spy = sinon.spy();
class Foo extends React.Component {
constructor(props) {
super(props);
this.componentWillUnmount = spy;
}
render() {
const { id } = this.props;
return (
<div className={id}>
{id}
</div>
);
}
}
Foo.propTypes = {
id: PropTypes.string.isRequired,
};
const wrapper = shallow(<Foo id="foo" />);
expect(spy).to.have.property('callCount', 0);
wrapper.unmount();
expect(spy).to.have.property('callCount', 1);

How to inject functions that change component state?

I want to keep some functions outside of my component for easier testing. However, I cannot change state with these functions because they cannot reference the component's state directly.
So I currently have the hacky solution where I set the function to a variable then call this.setState. Is there a better convention/more efficient way to do this?
Example function code in Tester.js:
const tester = () => {
return 'new data';
}
export default tester;
Example component code in App.js (without imports):
class App extends Component {
constructor() {
super();
this.state = {
data: ''
}
}
componentDidMount(){
let newData = tester();
this.setState({ data: newData })
}
render() {
return(
<div>{this.state.data}</div>
)
}
}
You could bind your tester function like this (this approach doesn't work with arrow functions):
function tester() {
this.setState({ data: 'new Data' });
}
class App extends Component {
constructor() {
super();
this.state = {
data: '',
};
this.tester = tester.bind(this);
}
componentDidMount() {
this.tester();
}
render() {
return (
<div>{this.state.data}</div>
);
}
}
But I would prefer a cleaner approach, where you don't need your function to access this (also works with arrow functions):
function tester(prevState, props) {
return {
...prevState,
data: 'new Data',
};
}
class App extends Component {
constructor() {
super();
this.state = {
data: '',
};
}
componentDidMount() {
this.setState(tester);
}
render() {
return (
<div>{this.state.data}</div>
);
}
}
You can pass a function to setState() that will return a new object representing the new state of your component. So you could do this:
const tester = (previousState, props) => {
return {
...previousState,
data: 'new data',
};
}
class App extends Component {
constructor() {
super();
this.state = {
data: ''
}
}
componentDidMount(){
this.setState(tester)
}
render() {
return(
<div>{this.state.data}</div>
)
}
}
The reason being that you now have access to your component's previous state and props in your tester function.
If you just need access to unchanging static placeholder values inside of your app, for example Lorem Ipsum or something else, then just export your data as a JSON object and use it like that:
// testData.js
export const testData = {
foo: "bar",
baz: 7,
};
...
// In your app.jsx file
import testData from "./testData.js";
const qux = testData.foo; // "bar"
etc.

Reset initial state in React + ES6

I have a class, ElementBuilder below, and when the user saves the Element they've built, I want the state to reset to the values below.
I have some functions in this class that I haven't provided but that change the state of title, size, and color.
In ES 5, I would have a getInitialState function on my class and could call this.getInitialState() in a function.
This element lives in my app for the lifecycle of a logged in user and I want the default values to always be the same regardless of past usage.
How do I achieve this without writing a function that sets an object of default values (or maybe that's the answer)? thanks!
class ElementBuilder extends Component {
constructor(props) {
super(props);
this.state = {
title: 'Testing,
size: 100,
color: '#4d96ce',
};
}
resetBuilder() {
this.setState({ this.getInitialState() });
}
}
You may use a getter function:
class ElementBuilder extends Component {
constructor(props) {
super(props);
this.state = this.initialState;
}
get initialState() {
return {
title: 'Testing',
size: 100,
color: '#4d96ce',
};
}
resetBuilder() {
this.setState(this.initialState);
}
}
or just a variable:
constructor(props) {
super(props);
this.initialState = {
title: 'Testing',
size: 100,
color: '#4d96ce',
};
this.state = this.initialState;
}
Using the proposed class fields, you could do something like this:
class ElementBuilder extends Component {
static initialState = {
title: 'Testing',
size: 100,
color: '#4d96ce'
}
constructor(props) {
super(props)
this.state = ElementBuilder.initialState
}
resetBuilder() {
this.setState(ElementBuilder.initialState)
}
}
Since the initial state doesn't seem to depend on anything instance specific, just define the value outside the class:
const initialState = {...};
class ElementBuilder extends Component {
constructor(props) {
super(props);
this.state = initialState;
}
resetBuilder() {
this.setState(initialState);
}
}
Use an High Order Component to clear component state (rerender)
Exemple Element.jsx :
// Target ----- //
class Element extends Component {
constructor(props){
super(props)
const {
initState = {}
} = props
this.state = {initState}
}
render() {
return (
<div className="element-x">
{...}
</div>
)
}
}
// Target Manager ----- //
class ElementMgr extends Component {
constructor(props){
super(props)
const {
hash = 0
} = props
this.state = {
hash, // hash is a post.id
load: false
}
}
render() {
const {load} = this.state
if (load) {
return (<div className="element-x"/>)
} else {
return (<Element {...this.props}/>)
}
}
componentWillReceiveProps(nextProps) {
const {hash = 0} = nextProps
if (hash !== this.state.hash) {
this.setState({load:true})
setTimeout(() => this.setState({
hash,
load:false
}),0)
}
}
}
export default ElementMgr

Categories