I am using enzyme. I need to test a component that has react router Link as a child. I need the following
mount the component using mount() of enzyme since I need to test the whole component tree
test behaviours of component when it properties change.
I cannot wrap my component with StaticRouter or MemoryRouter since enzyme only allows setProps() at root level.
My current solution is to stub the Link render method with sinon. Here is a short example.
import {Link} from 'react-router-dom';
import sinon from 'sinon';
// ....
// ....
describe('test',() => {
before(() => {
sinon.stub(Link.prototype, 'render').callsFake(function reactRouterLinkRender() {
const {innerRef, replace, to } = this.props;
const _props = {href: to, ref: innerRef, replace, onClick: this.handleClick};
return (<a {..._props}>this.props.children</a>);
});
});
});
Is there a better way to avoid the error "Invariant Violation: You should not use Link outside a Router"?
Thanks
Not sure if this helps anyone but for my case I managed to get around by shallow rendering the top level component and then using dive() to get to the parts I required.
loginPage = shallow(<LoginPage />);
....
loginPage.find(LoginForm).dive().find({name:"user"}).simulate("change", userEventMock);
Related
I'm new to React testing and with Jest and Enzyme.
I'm trying to learn how to use a TDD approach first and due to that, I'm building my tests before starting coding.
What I did was to create a sample app in React and I installed Enzyme dependencies and then I wrote the test:
import { shallow } from "enzyme";
import React from "react";
import AppLayout from "./AppLayout";
import { ContentLayout } from "./styles";
it("renders <AppLayout /> component", () => {
const wrapper = shallow(<AppLayout />);
expect(wrapper.find(ContentLayout)).to.have.lengthOf(1);
});
Then I built the component which contains a styled component called ContentLayout
import React from "react";
import { ContentLayout } from "./styles";
const AppLayout = () => {
return (
<>
<ContentLayout>
<h1>HELLO</h1>
</ContentLayout>
</>
);
};
export default AppLayout;
I'm unable yo make the test pass as what I got was the next error:
TypeError: Cannot read property 'have' of undefined
I would like to learn how shoulæd be the practice to test this kind of component and what rules to follow in general when I start a project from scratch with TDD in mind.
The AppLayout is called then in App.js
import React from "react";
import AppLayout from "./Components/AppLayout";
function App() {
return <AppLayout />;
}
export default App;
You should use .toHaveLength(number) matchers of expect in jestjs.
expect(wrapper.find(ContentLayout)).toHaveLength(1);
For nested components, there are two strategies generally:
Shallow Rendering API
Shallow rendering is useful to constrain yourself to test a component as a unit, and to ensure that your tests aren't indirectly asserting the behavior of child components.
This means we don't want to render the nested component(ContentLayout), we only test the behavior(lifecycle methods, event handlers, data fetching, condition render, etc.) of the parent component(AppLayout).
Full Rendering API (mount(...))
Full DOM rendering is ideal for use cases where you have components that may interact with DOM APIs or need to test components that are wrapped in higher order components.
I am writing a test for a component that is wrapped in a withStyles() from Material UI using Jest. I need to test the children elements, but my wrapper is undefined.
I've seen another post similar to writing tests with withStyles(), but the undefined error still persists.
Test File:
import { shallow } from 'enzyme';
import { TempComponent } from '../../../../src/components/helpers/temp';
describe('temp', () => {
let wrapper;
const renderComponent = () => shallow(<TempComponent />);
beforeEach(() => {
wrapper = renderComponent();
});
it('render correctly', () => {
expect(wrapper.type()).toEqual('div');
});
});
Component:
import React from 'react';
import { withStyles } from '#material-ui/core/styles';
const TempComponent = () => <button>Click Me!</button>;
export default withStyles({})(TempComponent);
I get this error for my test:
Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was undefined.
I am wanting the wrapper component to behave the same way as a wrapper without the withStyles() component. Any help would be appreciated!
Note: I am not doing snapchat testing with jest
This appears to be the issue and solution:
https://github.com/mui-org/material-ui/issues/9266#issuecomment-349447137
The issue isn't about the children elements but with the intermediary
element, it's creating. The shallow() API of enzyme only render one
level depth. Any higher-order component is going to obfuscate the
children. You have different workarounds. I would encourage you to
refer to the enzyme documentation. Still, here they are:
use mount()
use .dive()
use our createShallow() public API
use our internal until() helper
I run the test gives an error:
TypeError: Cannot set property 'background' of undefined
6 | componentDidMount() {
7 | const element = ReactDOM.findDOMNode(this)
> 8 | element.style.background = 'grey'
| ^
9 | }
code component:
import React, { Component } from 'react';
import ReactDOM from 'react-dom'
class Test extends Component {
componentDidMount() {
const element = ReactDOM.findDOMNode(this)
element.style.background = 'grey'
}
render() {
return (
<div className="Test">
test
</div>
);
}
}
export default Test;
code tests:
import React from 'react';
import Link from './Test';
import renderer from 'react-test-renderer';
it('renders correctly', () => {
const tree = renderer
.create(<Test/>)
.toJSON();
expect(tree).toMatchSnapshot();
});
This is a fairly simple component, and as I understand the jest cannot reach the DOM, how do I fix this problem? The simplest thing is that I thought of doing this as:
element.style && (element.style.background = 'gray')
First off, note that the ReactDOM.findDOMNode doc states:
findDOMNode is an escape hatch used to access the underlying DOM node. In most cases, use of this escape hatch is discouraged because it pierces the component abstraction. It has been deprecated in StrictMode.
So use of findDOMNode is to be avoided and using a ref is preferred.
Having said that, the reason why the test fails is because you are using react-test-renderer.
The overview in the docs states that react-test-renderer...
...makes it easy to grab a snapshot of the platform view hierarchy (similar to a DOM tree) rendered by a React DOM or React Native component without using a browser or jsdom.
In other words, react-test-renderer gives a "platform view hierarchy" similar to a DOM tree, but does not provide an actual DOM tree (or simulated DOM tree like jsdom).
So in this case element is not actually a DOM element and does not contain a definition for style which causes element.style.background to throw an error.
To test this code as written you would need to use something that renders to a DOM tree (or simulated DOM tree).
One of the most common ways to do that is to use mount from Enzyme which does a full DOM rendering using a simulated DOM provided by jsdom:
import React from 'react';
import Test from './Test';
import { mount } from 'enzyme';
it('renders correctly', () => {
const wrapper = mount(<Test />); // renders successfully
...
});
I have a React component with a modal dialog (built using reactstrap, but others have reported similar problems with react-bootstrap and other types of modal components). Enzyme cannot find any of the components inside the modal, even though they render fine in the actual app. Minimal example:
import React from 'react'
import { Modal } from 'reactstrap'
export default class MyModal extends React.Component {
render() {
return (
<div className="outside"> Some elements outside of the dialog </div>
<Modal isOpen={this.props.modalOpen}>
<div className="inside"> Content of dialog </div>
</Modal>
);
}
}
I would like to test the contents (in this case using jest) like this
import React from 'react'
import MyModal from './MyModal'
import { mount } from 'enzyme'
it('renders correctly', () => {
const wrapper = mount( <MyModal modalOpen/> );
expect(wrapper).toMatchSnapshot();
// Passes
expect(wrapper.find('.outside')).toHaveLength(1);
// Fails, 0 length
expect(wrapper.find('.inside')).toHaveLength(1);
});
The test finds the contents outside of the Modal correctly, but does not find anything inside. Looking at the snapshot shows that, indeed, nothing inside the <Modal> is rendered. However it does work if I replace mount with shallow. The problem with that is I need mount to test lifecycle methods like componentDidMount.
Why doesn't mount render the contents of the modal? I thought the whole point was that it rendered the entire tree of child elements.
Edit: This is no longer a problem in React 16 + Enzyme 3, because React 16 supports portal components.
In React 15 and before, the problem is that a modal dialog is (in most implementations) a portal component. This means it creates DOM elements that are attached directly to the document root, rather than being children of the parent React component.
The find method of the ReactWrapper created by mount looks through the DOM starting with the element created by the top level component, so it can't find the contents of the modal. But Enzyme's shallow doesn't attach to a DOM, and instead builds its own component tree which contains the modal contents.
To test a portal component, you first need to find the DOM elements that have been attached to the document body. Then you can create a new ReactWrapper around them so that all the usual Enzyme functions work:
import React from 'react'
import MyModal from './MyModal'
import { mount, ReactWrapper } from 'enzyme'
it('renders correctly', () => {
const wrapper = mount( <MyModal modalOpen/> );
expect(wrapper).toMatchSnapshot();
// Passes
expect(wrapper.find('.outside')).toHaveLength(1);
// Construct new wrapper rooted at modal content
inside_els = document.getElementsByClassName("inside")[0]
inside_wrapper = new ReactWrapper(inside_els, true)
// Passes
expect(inside_wrapper.find('.inside')).toHaveLength(1);
});
Currently, this is an open bug in Enzyme.
Update: It seems that Enzyme also leaves the modal attached to the DOM after the test finishes, so you may end up with multiple dialogs open in a later test. If this is a problem, you can clear the DOM after each test like this:
afterEach(() => {
var node = global.document.body;
while (node.firstChild) {
node.removeChild(node.firstChild);
}
});
try mocking the createPortal responsible for showing modals ReactDOM.createPortal = jest.fn(modal => modal);
In case you are using an older version of Enzyme, you can pass the container element to mount where you want your Modal to be rendered, like this:
import React from 'react'
import MyModal from './MyModal'
import { mount } from 'enzyme'
describe(() => {
let wrapper;
beforeEach(() => {
const container = document.createElement("div");
document.body.appendChild(container);
wrapper = mount( <MyModal modalOpen/> , {attachTo: container});
});
it('renders correctly', () => {
expect(wrapper).toMatchSnapshot();
// Passes
expect(wrapper.find('.outside')).toHaveLength(1);
// Passes now
expect(wrapper.find('.inside')).toHaveLength(1);
});
})
I am using jest and enzyme to test my react component which relies on antd - a 3rd react UI library.
For illustrative purpose, I produce the minimal code that is fair enough to show my question.
See my test component as follow:
import React from 'react';
import { Button } from 'antd';
function Test({onSubmit}) {
return (
<div>
<Button htmlType="submit" type="primary" />
</div>
);
}
export default Test;
Q1: I mock up the Button as below since unit test requires us to isolate the target and mock up any other dependencies.
Is that correct?
__mocks__
antd.js
antd.js
import mockComponent from '../mockComponent';
const list = [
'Form',
'Input',
'Button',
'Spin',
'Icon'
];
const mockups = list.reduce((prev, next) => {
prev[next] = mockComponent(next);
return prev;
}, {});
mockups.Form.Item = mockComponent('Form.Item');
export const Form = mockups.Form;
export const Input = mockups.Input;
export const Button = mockups.Button;
export const Spin = mockups.Spin;
export const Icon = mockups.Icon;
mockComponent.js
import React from 'react';
export default function mockComponent (name) {
return props => React.createElement(name, props, props.children);
}
Q2. I have got the following warning even if the test passes. How can I resolve that?
test.spec.js
import React from 'react';
import { shallow, mount } from 'enzyme';
import renderer from 'react-test-renderer';
import Test from '../components/question';
describe('mount test', () => {
let wrapper;
let props;
let mountedLogin;
const test = () => {
if (!mountedLogin) {
mountedLogin = mount(<Test {...props} />);
}
return mountedLogin;
};
beforeEach(() => {
mountedLogin = null;
});
it('always render test as the root', () => {
const divs = test().find('div');
expect(divs.length).toBeGreaterThan(0);
});
});
warning
console.error node_modules/fbjs/lib/warning.js:36
Warning: Unknown prop `htmlType` on <Button> tag. Remove this prop from the
element. For details, see
in Button (created by Unknown)
in Unknown (created by Test)
in div (created by Test)
in Test (created by WrapperComponent)
in WrapperComponent
A side note, I haven't got any jest configs in my package.json.
Can anybody help me out with these.
Many thanks
Q1: I mock up the Button as below since unit test requires us to
isolate the target and mock up any other dependencies. Is that
correct?
Currently, the recommended approach for React unit testing is shallow rendering. It basically renders the given component one level deep. If we shallow render your Test component, it will call render method of Test component, but not the render method of Button component. Even though Button is 3rd party component and dependency, we don't need to mock it. Because we don't render it. But still, we will be able to see whether it's in the component tree and it has got the correct props. This is what essentially we will assert with the mocking approach also. However, shallow rendering has few limitations also, but usually, it works very well for most of the scenarios.
But you can obviously mock children and render the whole component also. But it's time-consuming and problematic, at least with my experience.
Q2: I have got the following warning even if the test passes. How can
I resolve that?
Since you pass a string as name for React.createElement, React think you want to create a normal HTML element, and not a React component. But since there is a no HTML element call Button (case sensitive) and it doesn't know prop called htmlType, you get this unknown prop type warning. To prevent this warning, either you can stop passing props to React.createElement or pass a mock component to React.createElement instead of the name string.
import React from 'react';
function Mock(){
retun (<div/>);
}
export default function mockComponent (name) {
return props => {
return React.createElement(Mock, {...props, name}, props.children);
}
}
If you need to read more about react unit testing, I suggest you to go through this thread from react discuss forum.
Hope this helps!