How can I mock a component's state for unit testing? - reactjs

I am using enzyme, jest, sinon for unit testing. I would like to mock a components state and pass in a custom state before the component gets render. How can I achieve this?
My component starts off with some initial state:
constructor(props) {
super(props);
this.state = {
sample: "hello"
}
}
I want to overwrite the value of sample before render happens by mocking a fake state and before calling shallow().

Call setState directly on the component?
Not 100% sure, but maybe...
const myComponent = <MyComponent {...props} />
myComponent.setState({ ...mockState })
const myShallowRenderedComponent = shallow(myComponent)
const instance = myShallowRenderedComponent .instance()
expect(instance.state).toEqual(mockState)

Related

Props passed to child component are empty in constructor and componentDidMount()

I am just testing out various ideas and I do have experience using React, however, I seem to be totally missing something here.
In the ParentComponent I set initial state of an array within the constructor.
In componentDidMount() I then call a function to get some mock data which is just an array of objects. The function can be seen within ParentComponent. I then set the state using the mock data.
In the render() function of the ParentComponent I pass the array of objects as a prop to the ChildComponent.
In the ChildComponent when I try accessing the props within the constructor or within componentDidMount() the array is empty.
If I access the props in the render() function of the ChildComponent they are visible.
In React Dev Tools the props are clearly visible within the ChildComponent.
Does anyone have any idea why this behaviour is happening?
I need to be able to do some one time calculations on the data when the component is first mounted. Does anyone have any suggestions on how I can do this?
In all other languages I am aware of, if you pass arguments to a constructor they are immediately available.
class ParentComponent extends Component {
constructor(props) {
super(props);
this.state = {
data: []
}
}
componentDidMount() {
const mockData = this.getMockData();
this.setState({
data: mockData
});
}
render() {
return (<ChildComponent data={ this.state.data }/>);
}
getMockData = () => {
return [
{
key: 1,
name: "Test1",
price: "£0.00"
},
{
key: 2,
name: "Test2",
price: "£0.00"
}
]
}
}
export default ParentComponent;
class ChildComponent extends Component {
constructor(props) {
super(props);
console.log(props.data);
}
render() {
return (return some stuff here);
}
}
export default ChildComponent;
As you are populating the data array in componentDidMount lifecycle method, data array is initially empty when it is passed as a prop to the ChildComponent. This is because componentDidMount is called after the initial render.
So, when ChildComponent is mounted as a result of initial render of ParentComponent, data prop passed to ChildComponent is an empty array. Once the ChildComponent has mounted, then ParentComponent is mounted. At this point, componentDidMount method in ParentComponent is called.
You see the empty array in the constructor because it is only called once during the initial render of the ChildComponent. render() method in ChildComponent sees the latest data array because when componentDidMount is called in the ParentComponent, it updates the state, ParentComponent re-renders, leading to the re-render of ChildComponent with updated data array as a prop BUT the constructor in the ChildComponent is not called again, only render() method executes when ChildComponent re-renders.
To see the updated data in the ChildComponent, you can use one of the following options:
Use the data prop in the render() method
Use componentDidUpdate() lifecycle method
componentDidUpdate(prevProps, prevState) {
console.log(this.props.data);
}
Logging data prop in the render() method will first log the empty data array because of the initial render and then it will log the updated data array due to re-render of the ChildComponent.
Logging data prop in the componentDidUpdate() method will only log the updated data array because it is not called during the initial render of the component.
You assign a data to this.state.data in componentDidMount which is called as React completed to updated the DOM , so the props actually passed to the child component but their values in the moment of childComponenet construction still null because the componentDidMount still not called, and therefore the "this.getMockData()" not called yet

ReactJS lifecycle setState in componentDidMount

I have 2 components for demonstration of my problem:
Parent:
import React from "react";
import ReactDOM from "react-dom";
import { Grid, Row } from "react-flexbox-grid";
import Hello from "./Hello";
class App extends React.Component {
state = {
name: "Michal"
};
componentDidMount = () => {
this.setState({ name: "Tina" });
};
componentDidUpdate(prevState) {
console.log("App componentDidUpdate", prevState, this.state);
}
handleUpdate = value => {
console.log("App handleUpdate");
this.setState({ name: value });
};
render() {
return (
<Grid>
<Row>
<Hello name={this.state.name} update={this.handleUpdate} />
</Row>
</Grid>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
Child:
import * as React from "react";
class Hello extends React.PureComponent {
componentDidMount() {
// setTimeout(() => {
this.props.update("Matus");
// }, 0);
}
componentDidUpdate(prevProps) {
console.log("Hello componentDidUpdate", prevProps, this.props);
}
render() {
return <h1>Hello {this.props.name}!</h1>;
}
}
export default Hello;
In child component I want to set value in parent state via props function. But setState function is ignored, it works if props function is called from setTimeout.
Can you explain me why it work in setTimeout, why I should avoid this construction. And what is correct way to do it?
Hello component represent "Select", which in componentDidMount will fetch options and set default value.
Thank you.
Components initialise from the bottom up in React. So in your example Hello triggers componentDidMount, attempts to set the state in App via this.props.update, then App overrides it a split-second later when it calls its own componentDidMount. The name you set in the child component never reaches the state.
I'm not sure what the purpose of this is, hopefully only for leaning purposes as components shouldn't need to immediately set their own state when mounting. If you need to perform some logic before initialising the state in App you can use a constructor and do it there.
Regardless, the solution is remove the initial state setter in App.
It is not ignored and it does fire. You are just not observing it with your logs.
Check out:
https://codesandbox.io/s/kind-jackson-b2r2b?file=/src/App.js
In the console you will see the following execution order in the console window:
Hello componentDidMount props = Object {name: "Michal", update: function ()}
App handleUpdate value = Matus
App componentDidMount props = Object {}
Hello componentDidUpdate props = Object {name: "Tina", update: function ()}
App componentDidUpdate state = Object {}
Object {name: "Tina"}
Thus you will see the child componentDidMount fires and completes mount before the parent component completed and fires its componentDidMount, as components completes mounting from the child components up.
So you just never observe the state going to Matus because it triggers a new state change to Tina when it completes mounting.
You setState function from Hello component is ignored because of the React lifecycle. Basically App componentDidMount function overrides your state change from Hello component before it was rendered. That's why setTimeout helps, it moves your state change to the new rendering loop.
I don't know exact why you are trying to load data and pass it from the child component to parent but the good practice in React is to pass data from top to bottom. So the better solution would be to use Select component to just render the data from parent and react to user events.
<Select options={options} selected={option} handle={handleSelect} />
Reason:
React rendering is synchronous.
Rendering is a depth-first traversal
Now,
componentDidMount() {
this.props.update("Matus");
}
Is executed first, which sets the name Matus. Then the following executes -
componentDidMount = () => { this.setState({ name: "Tina" }); };
This sets the name Tina.
All of this happens on the first call-stack where the rendering happens. If we use setTimeout(), then
this.props.update("Matus");
will be moved to the second call-stack, which will be executed after the initial rendering and mounting has ended, thus setting the name Tina and triggering a re-render.
If you want to use class components, you need to use a constructor function to initialise state and pass the props from parent to child.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "Michal"
};
}
// ... rest of parent component
import * as React from "react";
class Hello extends React.PureComponent {
constructor(props) {
super(props)
}
componentDidMount() {
// setTimeout(() => {
this.props.update("Matus");
// }, 0);
}
componentDidUpdate(prevProps) {
console.log("Hello componentDidUpdate", prevProps, this.props);
}
render() {
return <h1>Hello {this.props.name}!</h1>;
}
}
export default Hello;

How to change the react state in testing

I have a below code,
export default class CampaignTrustSticky extends Component {
constructor(props) {
super(props);
this.state = {
showTrustBlock: false
};
}
render () {
let { showTrustBlock } = this.state;
return(
<section
className={classNames('trust-sticky', {
'pull-up': showTrustBlock
})}
>
</section>
)
}
}
in my test case i have used like this,
it('Render Campaing TrustKey', () => {
let wrapper = shallow(<CampaignTrustSticky />);
expect(wrapper.find('sectaion.pull-up')).not.toBe(null)
});
in this test it has been failed because of the default state value is set as false. So how can i change the state value as true from test case to succeed that case?
it('Render Campaing TrustKey', () => {
let wrapper = shallow(<CampaignTrustSticky />);
wrapper.setState({ showTrustBlock: true });
expect(wrapper.find('sectaion.pull-up')).not.toBe(null)
});
But your test code should test if your component works. You are changing the state in the test but the component doesn't change it`s state.
You should implement the functionality that change the state in your component and test this. For example a button press or something like that.
The answer by jonathanrz is correct. Although, your component isn't really written correctly. How can the state in that component change? It will always be false if it's written like that.
The component looks like something that should receive "showTrustBlock" as a prop, from a parent component.
So, the best thing would be to pass the "showTrustBlock" as a prop from a parent component, and then you can easily test it by just passing different props.
Also, if you do it like that, you can rewrite the component as a stateless functional component.

What's the difference between Enzyme simulation in shallow and mount wrappers?

Here's my component:
class App extends Component {
constructor(props) {
super(props);
this.state = {
value: 'foo'
};
}
onChange = (e) => {
this.setState({
value: e.currentTarget.value
});
};
render() {
return (
<div className="App">
<input
id="foo"
value={this.state.value}
onChange={this.onChange}
/>
</div>
);
}
}
Here's my test:
import {shallow, mount} from 'enzyme';
it('fires onChange', () => {
let wrapper = mount(<App />);
wrapper.find('#foo').simulate('change', {currentTarget: {value: 'bar'}});
expect(wrapper.state().value).toBe('bar');
expect(wrapper.find('#foo').props().value).toBe('bar');
});
The test presently fails:
Expected value to be (using ===):
"bar"
Received:
"foo"
But if I change mount to shallow, it passes. I'm not entirely sure why, and I'd like to know if there are any other practical differences between shallow and mount rendering.
For fixing the test, you can try:
import { mount } from 'enzyme';
it('fires onChange', () => {
const wrapper = mount(<App />).find('#foo');
expect(wrapper.props().value).toBe('foo');
wrapper.props().onChange({ currentTarget: { value: 'bar' } });
expect(wrapper.props().value).toBe('bar');
});
For clarification on difference between Shallow, Mount and render from enzyme
Shallow
Real unit test (isolation, no children render)
Simple shallow
Calls:
constructor
render
Shallow + setProps
Calls:
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
render
Shallow + unmount
Calls:
componentWillUnmount
Mount
The only way to test componentDidMount and componentDidUpdate. Full rendering including child components. Requires a DOM (jsdom, domino). More constly in execution time. If react is included before JSDOM, it can require some tricks:
`require('fbjs/lib/ExecutionEnvironment').canUseDOM = true;
Simple mount
Calls:
constructor
render
componentDidMount
Mount + setProps
Calls:
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
Mount + unmount
Calls:
componentWillUnmount
Render
only calls render but renders all children.
So my rule of thumbs is:
Always begin with shallow
If componentDidMount or componentDidUpdate should be tested, use
mount
If you want to test component lifecycle and children behavior, use
mount
If you want to test children rendering with less overhead than mount
and you are not interested in lifecycle methods, use render
There seems to be a very tiny use case for render. I like it because it seems snappier than requiring jsdom but as #ljharb said, we cannot really test React internals with this.
I wonder if it would be possible to emulate lifecycle methods with the render method just like shallow ? I would really appreciate if you could give me the use cases you have for render internally or what use cases you have seen in the wild.
I'm also curious to know why shallow does not call componentDidUpdate.
Kudos goes to https://gist.github.com/fokusferit/e4558d384e4e9cab95d04e5f35d4f913 and https://github.com/airbnb/enzyme/issues/465#issuecomment-227697726 this is basically a copy of the comment from the issue

React get props in child component for inner function use

i got a component A:
import React, { Component } from 'react'
import Gmap from '../global/gmap.component'
class RandomPlace extends Component {
render() {
return (
<Gmap address={this.state.random.location} />
which renders among other things, the Gmap component:
class Gmap extends Component {
componentDidMount () {
}
render() {
return (
<div className="gmap-component">
<p>{this.props.address}</p>
This <p>{this.props.address}</p> is well displayed and updated when i hit a "reload" button on component A. At this point, the React Chrome extension shows well the props' address content. And sees it being updated well on the "reload" action.
Problem is, i cant seem to be able to reach the props property address in the internal functions of my component Gmap like componentDidMount() or aCustomFunction().
I have tested this:
componentDidMount () {
console.log('gmap did mount')
this.setState({address: this.props.address})
let x = this.props.address
let y = this.state.address
console.log(x)
console.log(y)
With a constructor at the top of the class:
constructor (props) {
super(props)
this.state = {
address: 'xx'
}
But nothing shows up. I am new to React and sure i am missing something pretty basic but cant see to spot it.
Thanks in advance.
Are you asking how to call a custom function on your child component? If so,
<Gmap address={this.state.random.location} ref={(map) => { this.map = map; }} />
Then
this.map.aCustomFunction()
I'm not entirely sure, but i think it's going like:
In your component A, address={this.state.random.location} set from the state, as you see.
random object fill with call getRandomPlace() in componentDidMount().
So what's going on: at first you render Gmap component you have prop address there with undefined, because on component A didn't call componentDidMount() yet.
Then in component A trigger componentDidMount you get filled object "random" and yuor component Gmap recive normal prop addres, with no indefined. And rerender component with this new adderss prop, but your componentDidMount() in component Gmap has already invoked and doesn't trigger more...
If i'm right, you can set at component A
constructor(){
super();
this.state={
random: {location: "some test value"}
}
}
and you will see this "some test value" instead undefined in your console log.

Resources