Can't test Material-ui Modal with react test renderer - unit-testing

When trying to write a simple test with jest lib for Modal component like this
import { Modal } from '#material-ui/core';
import React from 'react';
import TestRenderer from 'react-test-renderer';
describe('Material Modal test', () => {
it('It should render', () => {
const testRenderer = TestRenderer.create(
<Modal open={true}>
<div>Test</div>
</Modal>
);
console.log(testRenderer.toJSON());
});
});
I get an error:
console.error node_modules/react-test-renderer/cjs/react-test-renderer.development.js:120
Warning: An invalid container has been provided. This may indicate that another renderer is being used in addition to the test renderer. (For example, ReactDOM.createPortal inside of a ReactTestRenderer tree.) This is not supported.
in div (created by ForwardRef(Modal))
in ForwardRef(Portal) (created by ForwardRef(Modal))
in ForwardRef(Modal) (at spinnerWaitingWindow.spec.tsx:10)
console.error node_modules/jsdom/lib/jsdom/virtual-console.js:29
Error: Uncaught [TypeError: parentInstance.children.indexOf is not a function]
Did anyone have a clue why this happens and how to setup test library?

Related

Enzyme / React Unit test

I have this React functional UI only component, which has two props passed in, the second being a function that is passed from its parent component. The onClick calls 'delegates' to a function in the parent container component, this parent method is then responsible for dispatching to a redux store.
import React, {Component} from 'react';
import PropTypes from 'prop-types';
const BotShowUI = ({ bot, onClick }) => {
return(
<div id={bot.id} onClick={onClick}>
{bot.id} : {bot.text}
</div>
)
}
BotShowUI.propTypes = {
bot: PropTypes.object.isRequired,
onClick: PropTypes.func.isRequired
};
export default BotShowUI;
My test spec is, which uses Jasmine
import React, {Component} from 'react';
import { mount } from 'enzyme';
import BotShowUI from '../botShowUI';
function onClickFunction(){};
describe('botShowUI', () => {
const bot = {id: 1, isDone: false, text: 'bot 123'};
const expectedDivText = '1 : bot 123';
const wrapper = mount(<BotShowUI bot={bot} onClick={onClickFunction} />);
it(' div has been rendered ', () => {
expect(wrapper.find('div').first()).not.toBe(null);
});
it(' div displays the correct bot text ', () => {
expect(wrapper.find('div').first().text()).toEqual(expectedDivText)
});
it(' div click event fired ', () => {
wrapper.simulate('click');
expect(wrapper.state('onClick')).toBe(true);
});
});
This last assertion fails with
Chrome 57.0.2987 (Windows 10 0.0.0) botShowUI div click event fired FAILED
TypeError: Cannot read property 'onClick' of null
at ReactWrapper.state (webpack:///~/enzyme/build/ReactWrapper.js:825:24 <- tests.webpack.js:26303:25)
at Object.<anonymous> (webpack:///app/react/components/bots/_tests/botShowUI.spec.js:25:23 <- tests.webpack.js:25415:25)
wrapper.simulate('click'); works, but the next line fails
What is the correct way to assert that the click was fired ?
Do I have to drop into wrapper's props/children instead of using state ?
I'm not trying to test the parent container in any way, the two are isolated.
This test is only concerned with this UI component.
First thing is that onClick isn't on state, but on props, so you will have to access it by doing wrapper.props('onClick').
Secondly, to test whether onClick has been handled or not is to use a spy, rather than an empty function. If you do not want to use spy, you can still do that, but not the way you have done. If you are interested, I can post some pseudo-code for that too. But coming back to using spies, you can use a spy as the onClick prop. Below is the code for that. I have hand-written it, so please check for any syntax error, but you should get the idea on what needs to be done.
it('should call the onClick handler on click', () => {
const onClickFunction = sinon.spy()
wrapper = mount(<BotShowUI bot={bot} onClick={onClickFunction} />)
wrapper.simulate('click');
expect(onClickFunction).toHaveBeenCalled();
})
Based on Abhishek's answer here's my solution for Jasmine
it(' div click event fired ', () => {
let onClickFunction_spy = jasmine.createSpy('onClickFunction');
const wrapper = mount(<BotShowUI bot={bot} onClick={onClickFunction_spy} />);
wrapper.simulate('click');
expect(onClickFunction_spy).toHaveBeenCalled();
});
Hope this helps anyone.

Testing a sub-component with a Link: '<Link>s rendered outside of a router context cannot navigate.'

I have a button component that creates a react-router Link element. It also allows an onClick function to be passed in for additional functionality (e.g. sending a Google Analytics event).
I have included this component in a parent, like so:
export default class Page extends Component {
const doSomething = () => {
//do a thing to test here
}
return (
<div>
<Button
onClickFn{() => doSomething()}
linkToUrl='/other/page' //this creates a <Link> inside the button
/>
</div>
)
}
Problem comes when I want to test that doSomething is being triggered correctly. I have used Enzyme mount to create the test Page component including the button. When I simulate a click I get the following error
'<Link>s rendered outside of a router context cannot navigate.'
because the Link in the button has no context. Is there a way of mocking this or preventing the error from showing? Or is there a better way of testing this functionality?
In your test, you will need to render the component within a <Router>. You can take a look at the tests for the <Link> component for examples on how to do this.
The basic idea is to create a memory history instance, pass that to the <Router>, and then render the <Link> inside of a <Route> passed to that. It sounds a bit involved, but it is fairly simple.
import { createMemoryHistory } from 'history'
it('clicks', () => {
const history = createMemoryHistory()
const App = () => (
<Router history={history}>
<Route path='/' component={Page} />
</Router>
)
})
Building on top of Paul's answer, here's a more detailed example for testing the onClick of a Button (or its Link child to be more precise). The example uses the testing libraries mocha (BDD test runner), chai (BDD assertions), enzyme (React testing utility), and sinon (test doubles).
import React from 'react';
import { Router, Route } from 'react-router';
import { createMemoryHistory } from 'history';
import MyCustomPage from '/.index';
describe('MyCustomPage', function(){
it('stores data when clicking the link', function() {
// Arrange
const Page = () => (
<MyCustomPage foo="foo" bar="bar" />
);
const container = enzyme.mount(
<Router history={createMemoryHistory()}>
<Route path="/" component={Page} />
</Router>
);
// Act
container.find('#my-link').simulate('click');
// Assert
expect(sessionStorage.setItem).to.have.been.calledWith('key', 'value');
});
});

How to test required props in React

I'm trying to write simple test with React and Jest.
Component:
import React from "react";
class Task extends React.Component {
render() {
let onDelete = this.props.onDelete;
return (
<li>
<div className="collapsible-header"><i className="material-icons" onClick={() => onDelete(this.props.taskId)}>delete</i>{this.props.title}</div>
<div className="collapsible-body"><p>{this.props.description}</p></div>
</li>
);
}
};
Task.propTypes = {
title: React.PropTypes.string.isRequired,
taskId: React.PropTypes.number.isRequired,
onDelete: React.PropTypes.func.isRequired,
description: React.PropTypes.string
};
Task.defaultProps = {
description: ''
};
export default Task;
Test
import React from 'react';
import Task from '../src/components/Task';
import renderer from 'react-test-renderer';
test('Task should require properties', () => {
const component = renderer.create( //this will give me React warnings which I would like to assert
<Task></Task>
);
});
Now I would like to assert that title, taskId and onDelete is required for Task component. That I will get React warning about not specifying them (or passing different types).
You can use a spy to find out if any kind of exception was thrown from react. Many people use a library called Sinon.js. From the documentation "A test spy is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls".
There is a great solution described in more detail here:
How to test React PropTypes through Jest?

How to test for a `click` event on the icon button of the `material-ui` AppBar?

I'm testing the following React component:
import React from 'react'
import AppBar from 'material-ui/lib/app-bar'
class NavBar extends React.Component {
render () {
return (
<div>
<AppBar
title='My NavBar Title'
/>
</div>
)
}
}
export default NavBar
It's composed of a material-ui AppBar component. Using Tape and Enzyme, I want to simulate a click on the AppBar's IconButton:
import NavBar from './NavBar'
import React from 'react'
import test from 'tape'
import { /* I don't know if it's `shallow` or `mount` */ } from 'enzyme'
test('NavBar component test', (assert) => {
test('simulating a click on the icon button', (assert) =>
// How do I do this?
// The following results in error:
// const wrapper = shallow(<NavBar />)
// wrapper.find('AppBar').find('IconButton').simulate('click')
assert.end()
})
assert.end()
})
How can I do it properly?
Obs: I'm searching for IconButton because, according to the React Dev Tools tab, that's the name of the rendered icon button component.
You should use mount for testing components below the top level of the component.
I found a way to test whether the function is called, not using the .simulate('event'), just invoke the method directly.
const wrapper = shallow(<NavBar />)
//use sinon.spy( object, method) to spy the method, instead of sinon.spy(func)
const spy = Sinon.spy(wrapper.renderer._instance._instance, 'click')
//inovke
wrapper.renderer._instance._instance.click()
expect(spy.called).to.be.true
You could find the method inside the .renderer._instance or its children _instance objects (depending on how deep the element is) and then use sinon.spy to spy this method.
I don't like this way, but this is the only way I know how to spy a method till now.

Unit testing React Bootstrap modal dialog

I'm trying to unit test React Bootstrap modal dialog using Jasmine. But it is not working as expected.
Here is jsfiddle link using latest versions of React, React Bootstrap, Jasmine.: http://jsfiddle.net/30qmcLyf/3/
Test which fails:
line# 27-28
// This test fails. Find DOM Node.
var instanceDomNode = ReactDOM.findDOMNode(instance);
expect(instanceDomNode).not.toBe(null);
line# 39-40
//This test fails. Find modal header.
var headerComponents = TestUtils.scryRenderedComponentsWithType(component, ReactBootstrap.Modal.Header);
expect(headerComponents.length).not.toBe(0);
Also what is wrong with line#35-36. If I uncomment lines I get error shown in comments.
// Error: Did not find exactly one match for componentType:function ModalHeader()...
//var headerComponent = TestUtils.findRenderedComponentWithType(component, ReactBootstrap.Modal.Header);
//expect(headerComponent).not.toBe(null);
As per latest official documentation for test utilities (link), you are supposed to pass ReactComponent as first argument.
Can somebody tell me what is wrong?
Check out how the react-bootstrap team writes tests for this. The modal is rendered into a different subtree which is how it gets rendered to the document body and not directly as a child of its parent. In other words your srcying fails because the component is not in that Component tree.
You can use refs on the modal or look for the DOM nodes directly in the document.
React-Bootstrap modal can be unit tested using mount of enzyme
it(componentToTest.title + 'renders Modal component', () => {
expect(wrapper.find(UVModal).length).toEqual(1);
});
it(componentToTest.title + 'renders major html elements', () => {
// Test whether modal-content element has 3 html children elements.
expect(wrapper.find('.modal-content').length).toEqual(1);
expect(wrapper.find('.modal-content').children()).toHaveLength(3);
// Test whether modal-header element has 2 html children elements.
expect(wrapper.find('.modal-header').length).toEqual(1);
expect(wrapper.find('.modal-header').children()).toHaveLength(2);
// Test whether modal-body element has 1 html child element.
expect(wrapper.find('.modal-body').length).toEqual(1);
expect(wrapper.find('.modal-body').children()).toHaveLength(1);
// Test whether modal-footer element has 1 html child element.
expect(wrapper.find('.modal-footer').length).toEqual(1);
expect(wrapper.find('.modal-footer').children()).toHaveLength(1);
elementToSearch = <p>Lannisters always pay their debt</p>;
expect(wrapper.contains(elementToSearch)).toEqual(false);
});
Check following blog for details:
https://medium.com/#yuvi1422/unit-test-react-bootstrap-modal-a37bf59732ab
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:
Actual Code:
------------
import React from 'react'
import { Modal } from 'reactstrap'
export default MyModal = () => {
return (
<Modal isOpen={props.isOpen}>
<ModalHeader>Header</ModalHeader>
<ModalBody>Body</ModalBody>
</Modal>
);
}
Unit Test:
----------
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 isOpen={true}/> , {attachTo: container});
});
it('renders correctly', () => {
expect(wrapper).toMatchSnapshot();
expect(wrapper.find('ModalHeader')).toHaveLength(1);
expect(wrapper.find('ModalBody')).toHaveLength(1);
});
})