I tried to use enzyme to simulate change event on a checkbox, and use chai-enzyme to assert if it's been checked.
This is my Hello react component:
import React from 'react';
class Hello extends React.Component {
constructor(props) {
super(props);
this.state = {
checked: false
}
}
render() {
const {checked} = this.state;
return <div>
<input type="checkbox" defaultChecked={checked} onChange={this._toggle.bind(this)}/>
{
checked ? "checked" : "not checked"
}
</div>
}
_toggle() {
const {onToggle} = this.props;
this.setState({checked: !this.state.checked});
onToggle();
}
}
export default Hello;
And my test:
import React from "react";
import Hello from "../src/hello.jsx";
import chai from "chai";
import {mount} from "enzyme";
import chaiEnzyme from "chai-enzyme";
import jsdomGlobal from "jsdom-global";
import spies from 'chai-spies';
function myAwesomeDebug(wrapper) {
let html = wrapper.html();
console.log(html);
return html
}
jsdomGlobal();
chai.should();
chai.use(spies);
chai.use(chaiEnzyme(myAwesomeDebug));
describe('<Hello />', () => {
it('checks the checkbox', () => {
const onToggle = chai.spy();
const wrapper = mount(<Hello onToggle={onToggle}/>);
var checkbox = wrapper.find('input');
checkbox.should.not.be.checked();
checkbox.simulate('change', {target: {checked: true}});
onToggle.should.have.been.called.once();
console.log(checkbox.get(0).checked);
checkbox.should.be.checked();
});
});
When I run this test, the checkbox.get(0).checked is false, and the assertion checkbox.should.be.checked() reports error:
AssertionError: expected the node in <Hello /> to be checked <input type="checkbox" checked="checked">
You can see the message is quite strange since there is already checked="checked" in the output.
I'm not sure where is wrong, since it involves too many things.
You can also see a demo project here: https://github.com/js-demos/react-enzyme-simulate-checkbox-events-demo, notice these lines
I think some of the details of my explanation might be a bit wrong, but my understanding is:
When you do
var checkbox = wrapper.find('input');
It saves a reference to that Enzyme node in checkbox, but there are times that when the Enzyme tree gets updated, but checkbox does not. I don't know if this is because the reference in the tree changes and therefore the checkbox is now a reference to a node in an old version of the tree.
Making checkbox a function seems to make it work for me, because now the value of checkbox() is always taken from the most up to date tree.
var checkbox = () => wrapper.find('input');
checkbox().should.not.be.checked();
checkbox().simulate('change', {target: {checked: true}});
///...
It is not bug, but "it works as designed".
Enzyme underlying uses the react test utils to interact with react, especially with the simulate api.
Simulate doesn't actually update the dom, it merely triggers react event handlers attached to the component, possibly with the additional parameters you pass in.
According to the answer I got here (https://github.com/facebook/react/issues/4950 ) this is because updating the dom would require React to reimplement a lot of the browsers functionality, probably still resulting in unforeseen behaviours, so they decided to simply rely on the browser to do the update.
The only way to actually test this is to manually update the dom yourself and then call the simulate api.
Below solution best worked for me:
it('should check checkbox handleClick event on Child component under Parent', () => {
const handleClick = jest.fn();
const wrapper = mount(
<Parent onChange={handleClick} {...dependencies}/>,); // dependencies, if any
checked = false;
wrapper.setProps({ checked: false });
const viewChildren = wrapper.find(Children);
const checkbox = viewChildren.find('input[type="checkbox"]').first(); // If you've multiple checkbox nodes and want to select first
checkbox.simulate('change', { target: { checked: true } });
expect(handleClick).toHaveBeenCalled();
});
Hope this helps.
This is what worked for me:
wrapper.find(CCToggle)
.find('input[type="checkbox"]')
.simulate('change', { target: { checked: true } })
CCToggle is my component.
Related
I have the following React component:
class Form extends React.Component {
constructor(props) {
super(props);
this.state = this._createEmptyTodo();
}
render() {
this.i18n = this.context;
return (
<div className="form">
<form onSubmit={this._handleSubmit.bind(this)}>
<input
placeholder={this.i18n.placeholders.addTitle}
type="text"
value={this.state.title}
onChange={this._handleTitleChange.bind(this)}></input>
<textarea
placeholder={this.i18n.placeholders.addDescription}
value={this.state.description}
onChange={this._handleDescriptionChange.bind(this)}></textarea>
<button>{this.i18n.buttons.submit}</button>
</form>
</div>
);
}
_handleTitleChange(e) {
this.setState({
title: e.target.value
});
}
_handleDescriptionChange(e) {
this.setState({
description: e.target.value
});
}
_handleSubmit(e) {
e.preventDefault();
var todo = {
date: new Date().getTime(),
title: this.state.title.trim(),
description: this.state.description.trim(),
done: false
};
if (!todo.title) {
alert(this.i18n.errors.title);
return;
}
if (!todo.description) {
alert(this.i18n.errors.description);
return;
}
this.props.showSpinner();
this.props.actions.addTodo(todo);
this.setState(this._createEmptyTodo());
}
_createEmptyTodo() {
return {
"pkey": null,
"title": "",
"description": ""
};
}
}
And the related test:
const i18nContext = React.createContext();
Form.contextType = i18nContext;
describe('The <Form> component', () => {
var wrapper;
var showSpinner;
var actions = {}
beforeEach(() => {
showSpinner = jest.fn();
actions.addTodo = jest.fn();
wrapper = mount(<i18nContext.Provider value={i18n["en"]}>
<Form
showModalPanel={showSpinner}
actions={actions} />
</i18nContext.Provider>);
});
test("validate its input", () => {
window.alert = jest.fn();
wrapper.find("button").simulate("click");
expect(window.alert.mock.calls.length).toBe(1);//<<< this FAILS!
});
});
This form, when the button gets clicked, it simply alerts a message using alert.
Now when I run the test I get this:
expect(received).toBe(expected) // Object.is equality
Expected: 1
Received: 0
Which is a failure because the mock does not get called apparently. But I promise you that the form component does alert a message when clicking on its button.
I suspect that, for some reasons, the mocked window.alert does not get used by the Form component when the click is performed programmatically using enzyme.
Anyone?
In Jest configuration with JSDOM global.window === global, so it can be mocked on window.
It's preferable to mock it like
jest.spyOn(window, 'alert').mockImplementation(() => {});
because window.alert = jest.fn() contaminates other tests in this suite.
The problem with blackbox testing is that troubleshooting is harder, also relying on the behaviour that expected from real DOM may cause problems because Enzyme doesn't necessary support this behaviour. It's unknown whether the actual problem, handleSubmit was called or not, that alert mock wasn't called is just an evidence that something went wrong.
In this case click event on a button won't cause submit event on parent form because Enzyme doesn't support that by design.
A proper unit-testing strategy is to set up spies or mocks for all units except tested one, which is submit event handler. It usually involves shallow instead of mount.
It likely should be:
jest.spyOn(window, 'alert').mockImplementation(() => {});
const formWrapper = wrapper.find(Form).dive();
jest.spyOn(formWrapper.instance(), '_handleSubmit');
formWrapper.find("form").simulate("submit");
expect(formWrapper.instance()._handleSubmit).toBeCalled();
expect(window.alert).toBeCalledWith(...);
State should be changed directly with formWrapper.setState instead of DOM events simulation.
A more isolated unit test would be to assert that form was provided with expected onSubmit prop and call formWrapper.instance()._handleSubmit(...) directly.
Instead of window, you can use global.
global.alert = jest.fn();
This is because browsers use the window name, while nodejs use the global name.
I have simple page with two divs. Background color of the second div depends on state of the active property. If active is true then it should use .active class from CSS file, otherwise use .two style.
I wrote unit test for this scenario to check if the style of the second div has been changed after state was changed.
I realized one thing, that when i execute style() function to get correct style name, unit test is not working and my style on second div is not updated. But when i execute style as an arrow function everything works. Do any of you know, why this happens? whats the problem with normal call of function? why render() is not called?
Arrow function console output (expected)
console.log src/containers/Example/Example.test.js:18
false
console.log src/containers/Example/Example.test.js:19
two
console.log src/containers/Example/Example.test.js:21
true
console.log src/containers/Example/Example.test.js:22
active
Normal function (wrong output)
console.log src/containers/Example/Example.test.js:18
false
console.log src/containers/Example/Example.test.js:19
two
console.log src/containers/Example/Example.test.js:21
true
console.log src/containers/Example/Example.test.js:22
two
Component with Arrow function
When you replace () => this.style() by this.style() unit test will fail.
import React, {Component} from 'react';
import styles from './Example.module.css';
class Example extends Component {
state = {
active: false
};
active = () => {
this.setState({active: !this.state.active});
};
style = () => {
return this.state.active ? styles.active : styles.two;
};
render() {
return (
<div>
<div onClick={() => this.active()} className={styles.one}/>
<div className={() => this.style()}/>
</div>
);
}
}
export default Example;
Unit test for Component
import React from 'react';
import Adapter from 'enzyme-adapter-react-16';
import {configure, mount} from 'enzyme';
import styles from './Example.module.css';
import Example from './Example';
configure({adapter: new Adapter()});
let component;
beforeEach(() => {
component = mount(<Example/>);
});
it('description', () => {
let two = component.find('div').at(2);
console.log(component.state().active);
console.log(two.props()["className"]());
component.setState({active: true});
console.log(component.state().active);
console.log(two.props()["className"]());
});
For second case this.style() you need to slightly modify console output
replace this console.log(two.props()["className"]); by this console.log(two.props()"className");
replace this console.log(two.props()["className"]); by this console.log(two.props()"className");
The problem isn't specific to unit testing but to the usage of functions in JavaScript. It would be applicable to production application as well.
onClick prop is expected to be a function. () => this.style() expression is a function. this.style() is the result of calling style method, a string.
Since style method is already bound to component instance (it's an arrow), it doesn't need to be wrapped with another arrow. It should be:
<div className={this.style}/>
I need to access the state of the inner component, to make it active for click event, my problem is Enzyme does not allow this when using mount, this can only be achieved by shallow rendering of enzyme as mentioned over here, also as mentioned I have tried to use dive to fetch the Form component and again from Form to get Button component which I need to reach, the problem is that my test case keeps on failing as Form component length is zero.
enzyme: 3.1.0
enzyme-adapter-react-15: 1.0.1"
I am pretty new to Enzyme, Any help will be appreciated, Thanks
contactus.test.js :
test('It should simulate submit action ', ()=>{
let contactUs = shallow(<ContactUs />);
sinon.spy(ContactUs.prototype, 'submitMessage');// Verify this method call
let form = contactUs.find(Form)
expect(form.length).toBe(1);//Failing over here
let button = form.dive().find(Button);
expect(button.length).toBe(1);
button.setState({disabled : false});//Need to achieve this
expect(button).toBeDefined();
expect(button.length).toBe(1);
expect(button.props().label).toBe('SEND MESSAGE');
button.find('a').get(0).simulate('click');
expect(ContactUs.prototype.submitMessage).toHaveProperty('callCount',
1);
});
contactus.js :
import React, {Component,PropTypes} from 'react';
import Form from './form';
import {sendSubscriptionMessage} from '../../network';
import Button from '../Fields/Button';
export default class ContactUs extends Component {
constructor(props) {
super(props);
this.state = {
contactData: {}
}
}
onChangeHandler(event) {
let value = event.target.value;
this.state.contactData[event.target.name] = value;
}
submitMessage(event) {
event.preventDefault();
sendSubscriptionMessage(this.state.contactData);
}
render() {
return (<div className = "row pattern-black contact logo-container" id = "contact">
<div className = "container" >
<h2 className = "sectionTitle f-damion c-white mTop100" >
Get in Touch!
<Form onChangeHandler = {
this.onChangeHandler.bind(this)
} >
<Button onClick = {
this.submitMessage.bind(this)
}
className = "gradientButton pink inverse mTop50"
label = "SEND MESSAGE" / >
</Form> </div>
</div>
);
}
}
First of all I think you should not test the Button and the Form functionalities here. In this file you should test only the ContactForm component.
For the first fail, this should work:
find('Form') (the Form should have quotes)
Same for the button:
find('Button');
In this way you don't even have to import the Form and the Button components in your test file at all;
Then, you don't have to set any state for that button. You test the button functionality in the Button.test.js file.
All you have to do here is to call its method like this:
button.nodes[0].props.onClick();
Overall, this is how your test should look like ( note that I didn't test it, I've been using Jest for testing my components, but the logic should be the same ):
test('It should simulate submit action ', ()=>{
const wrapper = shallow(<ContactUs />);
const spy = sinon.spy(ContactUs.prototype, 'submitMessage'); // Save the spy into a new variable
const form = wrapper.find('Form') // I don't know if is the same, but in jest is enough to pass the component name as a string, so you don't have to import it in your test file anymore.
expect(form).to.have.length(1);
const button = wrapper.find('Button'); // from what I see in your code, the Button is part of the ContactUs component, not of the Form.
expect(button).to.have.length(1);
/* These lines should not be part of this test file. Create a test file only for the Button component.
button.setState({disabled : false});
expect(button).toBeDefined();
expect(button.length).toBe(1);
expect(button.props().label).toBe('SEND MESSAGE');
*/
button.nodes[0].props.onClick(); // Here you call its method directly, cause we don't care about its internal functionality; we want to check if the "submitMessage" method has been called.
assert(spy.called); // Here I'm not very sure... I'm a Jest fan and i would have done it like this "expect(spy).toHaveBeenCalled();"
});
How do I test input.focus() in enzyme. I am writing the script with react. My code is below:
public inputBox: any;
componentDidUpdate = () => {
setTimeout(() => {
this.inputBox.focus();
}, 200);
}
render() {
return (
<div>
<input
type = 'number'
ref = {element => this.inputBox = element } />
</div>
);
}
You can use mount instead of shallow.
Then you can compare document.activeElement and the input DOM node for equality.
const output = mount(<MyFocusingComponent/>);
assert(output.find('input').node === document.activeElement);
See https://github.com/airbnb/enzyme/issues/316 for more details.
Per React 16.3 updates... using createRef for anyone visiting this post today, if you rearrange the original component to use the new ref api
class InputBox extends PureComponent {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
componentDidMount() {
this.inputRef.current.focus();
}
render() {
return (
<input
ref={this.inputRef}
/>
);
}
}
Then in your test spec
it("Gives immediate focus on to name field on load", () => {
const wrapper = mount(<InputBox />);
const { inputRef } = wrapper.instance();
jest.spyOn(inputRef.current, "focus");
wrapper.instance().componentDidMount();
expect(inputRef.current.focus).toHaveBeenCalledTimes(1);
});
Notice the use of the inputRef.current attribute which references the currently assigned DOM node.
Other approach is to test if element gains focus, i.e. focus() is called on node element. To achieve this, focused element need to be referenced via ref tag like it takes place in your example – reference was assigned to this.inputBox. Consider example below:
const wrapper = mount(<FocusingInput />);
const element = wrapper.instance().inputBox; // This is your input ref
spyOn(element, 'focus');
wrapper.simulate('mouseEnter', eventStub());
setTimeout(() => expect(element.focus).toHaveBeenCalled(), 250);
This example uses Jasmine's spyOn, though you can use any spy you like.
I just had the same issue and solved using the following approach:
My setup is Jest (react-create-app) + Enzyme:
it('should set the focus after render', () => {
// If you don't create this element you can not access the
// document.activeElement or simply returns <body/>
document.body.innerHTML = '<div></div>'
// You have to tell Enzyme to attach the component to this
// newly created element
wrapper = mount(<MyTextFieldComponent />, {
attachTo: document.getElementsByName('div')[0]
})
// In my case was easy to compare using id
// than using the whole element
expect(wrapper.find('input').props().id).toEqual(
document.activeElement.id
)
})
This worked for me when using mount and useRef hook:
expect(wrapper.find('input').get(0).ref.current).toEqual(document.activeElement)
Focus on the particular element can be checked using selectors.
const wrapper = mount(<MyComponent />);
const input = wrapper.find('input');
expect(input.is(':focus')).toBe(true);
Selecting by data-test attribute or something similar was the most straight forward solution I could come up with.
import React, { Component } from 'react'
import { mount } from 'enzyme'
class MyComponent extends Component {
componentDidMount() {
if (this.inputRef) {
this.inputRef.focus()
}
}
render() {
return (
<input data-test="my-data-test" ref={input => { this.inputRef = input } } />
)
}
}
it('should set focus on mount', () => {
mount(<MyComponent />)
expect(document.activeElement.dataset.test).toBe('my-data-test')
})
This should work
const wrapper = mount(<MyComponent />);
const input = wrapper.find('input');
expect(input).toHaveFocus();
I have a component that uses contentEditable as an input method. The part from the component that is of interest is:
<div className="enter-edit-mode" onClick={view.enterEditMode}>
<div className="user-input" ref="content" contentEditable onInput={view.textChanged}></div>
</div>
The component works fine - it gets into the textChanged method on user input. The method looks like this:
textChanged: function (e) {
var view = this,
textValue = e.target.innerHTML;
view.setState({
enteringText: textValue.length,
temporaryValue: textValue
});
}
The problem I'm facing appears when I try to test the input behavior. The setup is done with enzyme, chai, sinon. I'm rendering the component using a simple renderComponent function, using enzyme's mount method.
beforeEach(function () {
view = renderComponent(card);
viewReact = view.get(0);
});
it('should enter text changed method on input', function () {
let spy = sinon.spy(viewReact, 'textChanged');
view.find('.user-input').simulate('input');
expect(spy).to.have.been.called;
spy.restore();
});
It outputs expected textChanged to have been called at least once, but it was never called. The weird part is, however, if I put a console.log inside the component's method, it gets there.
What I've tried to make it work
use sinon.stub instead of spy, as I though that maybe something in my method doesn't work properly
call it with view.find('.user-input').simulate('input', {target: {value: "lorem"}) or .simulate('input', {key: 'a'})
If instead of simulating the input I do a viewReact.textChanged(), it obviously works.
I'm guessing that it's the input method on contentEditable that's causing this. Any suggestions? How can I properly enter text in the onInput method? (even if it gets in the textChanged method, the text is empty)
I could reproduce your issue trying to test the following component (which looks similar to yours):
const MyComponent = React.createClass({
textChanged(e) { console.log('text changed') },
render() {
return (
<div className="enter-edit-mode">
<div className="user-input" ref="content" contentEditable onInput={ this.textChanged }></div>
</div>
);
}
});
I also managed to get the test working, in a somewhat convoluted way:
it('should enter text changed method on input', () => {
let view = mount(<MyComponent/>);
let spy = sinon.spy(view.instance(), 'textChanged');
view = view.mount();
view.find('.user-input').simulate('input');
expect(spy).to.be.called;
spy.restore();
});
The view.mount() to re-mount the component seems to do the trick here.
I'm wasn't familiar with Enzyme at all (although I like it :), but it looks like it's adding various layers around components, in which case it's easy for Sinon spies to get "lost".
One possible caveat: I did all of this testing in Node using jsdom, and not in a browser.
I made a little test, I don't have sinon but I found a use case:
class Home extends Component {
state = {
status: 'default'
}
textChanged = () => {
this.setState({
status: 'changed'
})
}
render () {
return (
<div className="enter-edit-mode" >
<div className="user-input" ref="content" contentEditable onInput={this.textChanged}>Hello</div>
<span>{this.state.status}</span>
</div>
)
}
}
And the test
describe('component', () => {
it('should say changed', () => {
const component = shallow(<Home />);
expect(component.find('span').text()).toEqual('default');
component.find('.user-input').simulate('input', {key: 'a'})
expect(component.find('span').text()).toEqual('changed');
});
});
It passed all is expected