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

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

Related

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;

Enzyme: Test children rendering failling with mount but passes with shallow

I'm using Enzyme + Jest to test some React components.
I have the following test:
describe('<Modal />', () => {
let wrapper;
it('should render children props', () => {
wrapper = shallow(<Modal />);
wrapper.setProps({
children: <div className='should-render'>This should be rendered</div>
});
expect(wrapper.find('.should-render').length).toEqual(1);
});
});
And it works just find. But, if I replace the shallow method from enzyme with mount the test fails (can't find a element with .should-render class).
Is this the expected behavior? I though that the difference between shallow and mount was the ability to access lifecycle methods but render worked the same.
Ok. So the problem was my lack of understanding of how mount works.
My Modal component has a state variable called show that prevents the element from mounting if it's set to false (I'm wrapping react-boostrap modal component, that has this beahavior). By default, this state variable it's false and since the children are being rendered in the body of the modal, no children were found because the element wasn't beign mounted.

Enzyme: How can I test a component with DOM side-effect?

Say I have a component like so -
// #flow
import React, { PureComponent } from 'react';
export default class ReplaceLink extends Component {
containerRef = React.createRef();
componentDidMount() {
const links =
Array.from(this.containerRef.current.getElementsByTagName('a'));
links.forEach(a => a.setAttribute('href', 'dummylink'));
}
render = () => <div ref={this.containerRef}>{this.props.children}</div>;
}
which replaces href of links placed within it. But even when doing full dom rendering in enzyme, when I do a wrapper.debug() to see the result of the render, I still see original links only.
I've tried doing a force wrapper.update and using setTimeouts, but it just doesn't reflect the expected link.
One of reasons why direct DOM access is discouraged in React is that it makes testing more complicated.
The component can be rendered with skipped componentDidMount:
const wrapper = shallow(<ReplaceLink/>, { disableLifecycleMethods: true })
Then a ref can be mocked and componentDidMount can be called manually:
const setAttribute = jest.fn();
const getElementsByTagName = jest.fn().mockImplementation(() => [{ setAttribute }]);
wrapper.instance().containerRef.current = { getElementsByTagName };
wrapper.instance().componentDidMount();
Then stubbed DOM functions can be asserted that they were called.
Found the best way to test something like this is through the getDOMNode method.
First, make sure to use mount to render the wrapper, so we have a simulated DOM environment to query against.
Next, use wrapper.getDOMNode() to get the underlying DOM node.
Any changes made during the lifecycle methods to the underlying DOM will be reflected in this DOM reference.
Use .querySelector, or <insert-dom-query-method> to make assertions.
const wrapper = mount(
<ReplaceLink>
Google
</ReplaceLink>
);
const linkTags = wrapper.getDOMNode().querySelectorAll('a');
linkTags.forEach(tag => {
expect(tag.getAttribute('href')).toBe('dummy');
});

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

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)

How to you test a redux dumb component that renders smart components?

A "dumb" React view can be testing by passing it props before rendering it with enzyme/jsdom. Snapshot testing can then be used to validate the behavior (as in jest).
A smart component composed of a 'dumb view' connected with mapStateToProps/mapDispatchToProps can be tested by:
unit testing mapXxxToProps to check the logic is right
testing the dumb view
and optionnaly:
render the smart component by wrapping it in a Provider
However, this seems to break whenever a dumb view starts to nest other smart containers ; the rendering of the dumb view is not possible simply with props, since some child components down the chain might need a store.
Is there a way around that ?
Am I missing something ?
With enzyme, you can use a shallow render to test the dumb component is rendering the expected smart components, without actually rendering them completely.
Component (Bar is a smart component):
const Foo = () => {
return (
<div>
<Bar />
</div>
)
}
Test:
import { shallow } from 'enzyme';
...
it('should render <Foo /> component', () => {
const wrapper = shallow(<Foo />)
expect(wrapper.contains(<Bar />)).to.be.true
})

Resources