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.
Related
I'm trying to write a unit test for a Card compenent using Jest. This component takes a framer-motion value and a ref as Props. This values can only be created using the useMotionValue and useRef hook.
interface Props {
currentIndex: MotionValue<number>;
containerRef: RefObject<HTMLDivElement>;
}
Currently, I'm able to pass in a dummy value for the ref using document.createElement, but when i tried creating a motion value to pass into the component, I get an error saying that react hooks cannot be used ouside a component
it("renders a card", () => {
const container = document.createElement("div");
render(
<Card
containerRef={{ current: container }}
currentIndex={******} // motion value goes here
/>
);
});
I had to render the component in a parent component first and then test the parent Component but i was wondering, is there a way to mock the motion value and pass it in as a prop?
The easiest way would be to mock useMotionValue implementation. You can use jest.mock at the top of your file where you write your test for the component that uses this hook. Remember also to not mock other functionality by requiring actual implementation.
// at the top of the file that tests component with useMotionValue call
jest.mock('framer-motion', () => ({
...jest.requireActual('framer-motion'),
useMotionValue: jest.fn().mockReturnValue({
// put here the mock implementation of MotionValue methods
})
}));
I have been looking at a lot of examples for testing react and redux applications using Enzyme and Jest, and there are very few that even mention code cleanup. When using shallow or mount do you not explicitly need to call unmount or detach to try to keep the memory bloat and runtime down?
There is a case where we will want to use detach to cleanup. Let's look at the simple shallow and mount case first.
Assigning to variables
This 'simple' case is where we simply assign the render to a var/const/let.
If we look at the (cut down) example of using Jest and Enzyme from the Enzyme Github.
describe('<MyComponent />', () => {
it('renders three <Foo /> components', () => {
const wrapper = shallow(<MyComponent />);
expect(wrapper.find(Foo)).to.have.lengthOf(3);
});
it('renders an `.icon-star`', () => {
const wrapper = shallow(<MyComponent />);
expect(wrapper.find('.icon-star')).to.have.lengthOf(1);
});
});
We can see the shallow method being called and the result being assigned to a const. The result is a ShallowWrapper object.
As const has a block scope when the execution leaves the block it is defined - in this case the test arrow function - the Javascript engine will automatically deallocate the ShallowWrapper memory.
It's because of this we don't need to worry about unmounting - this is only used to test specific Component lifecycle methods.
Attaching Components to DOM
We can also attach components to the DOM.
If we look at the test case automatically generated by the create-react-scripts.
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
This example doesn't use Enzyme, but it's the same concept as to when you use attachTo in the Enzyme mount function.
We can see our Component is being attached to a div in the document and then ReactDOM.unmountComponentAtNode being called to cleanup. This is actually what detach calls.
We need to do this cleanup because a reference to our rendered Component exists outside our block scope and therefore will not be deallocated when execution exits this block.
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
I have a component which has 2 input text fields.
In the componentDidMount() method I am calling this.refs.password.focus();
There is some amount of tricky business logic sitting inside my componentDidMount but while doing a shallow unit test on componentDidMount, I get error
cannot access password of undefined
I checked the instance of the shallow component and see that this.refs is undefined.
My question is how can we set this through tests?
Shallow has a second parameter which we can pass called as the context where we can set the context for unit testing but it seems to be doing nothing.
Any help around this area would be highly appreciated.
The easiest approach for this problem would be to set the refs through the instance.
const component = shallow(<Component />)
const instance = component.instance()
instance.refs = {
password: {
getRenderedComponent: jest.fn(() => ({
focus: jest.fn
}))
}
}
shallow does not have a ref method. You need to use mount to test the rendering in its entirety.
References:
API docs:
https://github.com/airbnb/enzyme/blob/master/docs/api/shallow.md
https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md
Issue:
https://github.com/airbnb/enzyme/issues/316
I'm trying to pass context to a React component, but because I am testing with Enzyme, I'd like to add the component to its parent dynamically so I can examine its state. The test looks like this:
describe('<BeaconConfig />', () => {
it('inherits the config from BeaconConfig', () => {
mount(<BeaconConfig persistent><div id="parent"></div></BeaconConfig>, { attachTo: document.body });
const wrapper = mount(<Beacon/>, { attachTo: document.getElementById('parent') });
expect(wrapper.state('persistent')).to.be(true);
});
});
The test fails because the persistent property of the Beacon component's state is undefined, although it should be inherited from BeaconConfig via context.
When I try putting Beacon directly inside the JSX when I mount BeaconConfig then it works fine, but in this case Enzyme won't let me get at the Beacon component state since it isn't the root.
Is it normal that React isn't propagating the context to my component when I add it dynamically to its parent?
It is normal that React isn't propagating the context - it doesn't look at the DOM and diff it with its VDOM in that way.
You'll want make it a child in the initial mount, and use the .find() or .children() methods of the MountWrapper (docs) to dig through the children, find the Beacon and do your assertions.
Here is the complete test I ended up using:
describe('Context', () => {
let wrapper;
let parent;
const open = stub().returns({});
const mockIndexedDB = { open };
const config = mount(<BeaconConfig persistent position="bottom" indexedDB={mockIndexedDB} />);
beforeEach(() => {
parent = document.createElement('div');
document.body.appendChild(parent);
wrapper = mount(<Beacon>some text</Beacon>, {
attachTo: parent,
context: config.instance().getChildContext()
});
});
afterEach(() => {
wrapper.detach();
document.body.removeChild(document.body.firstChild);
});
it('overrides the default values with the context if any', () => {
expect(wrapper.state('persistent')).to.be(true);
expect(wrapper.state('position')).to.be('bottom');
expect(open.calledOnce).to.equal(true);
});
});
#STRML had a good suggestion but I don't think that it is possible to access the state of a non-root component.
Instead I instantiate BeaconConfig in isolation and grab it's child context, passing that manually to Beacon using the options parameter of mount. This tests that BeaconConfig creates the right child context and that Beacon uses the context correctly. It doesn't test that BeaconConfig passes the config down to Beacon when the latter is a descendant, but we can presumably take that for granted since it is basic React functionality.