React Unit test function vs ArrowFunction - reactjs

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}/>

Related

React event handler works without bind()

I'm working through a react tutorial and the instructor is showing that the event handler in this code won't work, because this() is accessing the outer environment. But I get no error. Can someone explain it to me?
import React, { Component } from "react";
class Counter extends Component {
state = {
count: 0,
};
handleIncrement() {
console.log(this.state);
console.log(this.props);
}
render() {
return (
<div>
<button onClick={this.handleIncrement()}>
Increment
</button>
</div>
);
}
}
export default Counter;
The thing is, when your event handler needs to access this from a local scope, and you call the method like this; this.handleClick(), you are telling JavaScript to implement the task of the method IMMEDIATELY it gets there (in your case, immediately it is rendered), which conventionally, doesn't require binding to this.
But when you 'call' (I put it in quotations because the right word should be REFER) a method like this; this.handleClick, you are actually referring to the method (meaning it should be invoked only when the user does something), not invoking it immediately. This either requires binding this.handleClick= this.handleClick.bind(this); or the use of arrow function for your method handleClick = () => {};. It is mostly used for onClick functionalities.
You are not getting that error in your code because you included the parentheses - this.handleIncrement(). If you remove the parentheses and still consoleLog this.state in your handleIncrement, you will definitely get undefined error. But if your handleIncrement is only logging something outside a state, you will not get the error.
If you understand my basic explanation, kindly accept the answer.
Any function called directly from render method will get the container object as this
But when we assign a function to onClick event, we don't want to call that function immediately... so we assign it like this
<button onClick={this.handleIncrement}>
(only the function name without () at the end) ... and this says to call the function when the button is clicked.
But when you click the button the function will not be called from the render method anymore so the this reference will be changed and produce an error.
In your case, you added the () to your this.handleIncrement function invoking it immediately... so it's not causing any problem but it will give you wrong results in almost all cases since it won't get called on click but it will get called with each render.
Since your simple code gets rendered only on button click it's probably correcting the problem. Add a second button and it will give wrong result or the UI will freeze.
The correct way is to remove the () after this.handleIncreament and bind the function inside constructor ... this.handleIncreament = this.handleIncreament.bind(this)
Without bind() method you can use directly arrow function in handleincrement.
check below code
const { Component } = React;
class Counter extends Component {
state = { count: 0 };
handleIncrement=()=> {
const { count } = this.state;
this.setState({ count: count + 1 });
}
render () {
return <label>
<div>Count {this.state.count}</div>
<button onClick={this.handleIncrement}>Increment</button>
</label>;
}
}
ReactDOM.render(<Counter/>, document.querySelector('main'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<main/>
I agree with Afzal Hossain.
<button onClick={this.handleIncrement()}>
This line will call handleIncrement function on render. This is not the correct way to add an event.
<button onClick={this.handleIncrement}>
This will be the correct approach to call the function. But since it's a callback, it will have no knowledge of what this is since it's not in the same context.
React Documentation makes it really clear why we should always bind callback functions with this to have the context available in that particular function.
However, if you don't want to bind your function, there are two workarounds mentioned in react documentation
Public Class Fields syntax
import React, { Component } from "react";
class Counter extends Component {
state = {
count: 0,
};
handleIncrement = () => {
console.log(this.state);
console.log(this.props);
}
render() {
return (
<div>
<button onClick={this.handleIncrement}>
Increment
</button>
</div>
);
}
}
export default Counter;
ReactDOM.render(<Counter/>, document.querySelector('main'));
Arrow functions
import React, { Component } from "react";
class Counter extends Component {
state = {
count: 0,
};
handleIncrement() {
console.log(this.state);
console.log(this.props);
}
render() {
return (
<div>
<button onClick={() => this.handleIncrement()}>
Increment
</button>
</div>
);
}
}
export default Counter;
For more details, refer to this documentation link.
As Afzal Hossain says, you're invoking this.handleIncrement() when the element renders rather than when the button is clicked.
You need to provide the function handle itself to onClick, and bind() it to the correct context when it is constructed, so that this always accesses the instance of Counter within handleIncrement().
Here is a working implementation of the suggestions made in his answer:
const { Component } = React;
class Counter extends Component {
state = { count: 0 };
handleIncrement = this.handleIncrement.bind(this);
handleIncrement () {
const { count } = this.state;
this.setState({ count: count + 1 });
}
render () {
return <label>
<div>Count {this.state.count}</div>
<button onClick={this.handleIncrement}>Increment</button>
</label>;
}
}
ReactDOM.render(<Counter/>, document.querySelector('main'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<main/>

Using JS to create elements with onClick attribute to make function calls when used in a React Component

I am sending multiple results from a search query as 'item' to a function which generates elements. However, the function turns the onClick attribute value I assign into a string, hence each onClick event will not be recognised as a function call in JSX
The function in question is within renderResults.js:
function searchItemRenderer (item) {
/*The container for all elements that will be rendered at #results*/
var html = [];
/*1st element to be added*/
html.push("<p class='images'>");
/*Subsequent 'html' array elements to be added from 'item' to form <img/> elements with attributes*/
html.push("<img src="+item.thumbnailUrl+" id="+item.index+" onClick={this.logMessage}>");
return html.join("");
}
export default function showResults () {
document.getElementById("results").appendChild(searchItemRenderer())
}
renderResults.js is then imported into a .jsx file containing the target component:
import React, { PureComponent } from "react";
import {showResults} from "./renderResults.js";
class Results extends Component {
constructor(props){
super(props);
this.logMessage = this.logMessage.bind(this);
}
logMessage(){
console.log("Image clicked!");
}
render() {
return (
<div id="results" onLoad={showResults}>
</div>
);
}
}
export default Results;
I tried inserting {this.logMessage} as a Javascript variable as shown below but it did not render:
html.push("<img src="+item.thumbnailUrl+" id="+item.index+" onClick="+{this.logMessage}+">");
Is there some way to have {this.logMessage} rendered as a React onClick function call event by the JS function? Should this work, "Image clicked!" should log into the console each time an image rendered in the browser is clicked
Hope it will help you
html.push(<p class='images'>);
You don't need to push html string, just push jsx and also you don't need html.join("");
just return html
don't forget to add corresponding closing tags. :)

Enzyme- How to set state of inner component?

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();"
});

scryRenderedComponentsWithType returns an empty array

I have the following test for a React app:
it('has 7 NavBarItems', () => {
const component = ReactTestUtils.renderIntoDocument(<App />);
const navBarItems = ReactTestUtils.scryRenderedComponentsWithType(
component,
NavBarItem);
expect(navBarItems.length).toEqual(7);
});
When the App component is rendered in a non-test environment, a NavBar with 7 NavBarItem child components is created at the top of the screen, to facilitate navigation throughout the app.
The NavBarItem class is as follows:
class NavBarItem extends Component {
render() {
return (
<div id={this.props.id}
className={"NavBarItem" + (this.props.active ? " active" : "") + (this.props.visible ? "" : " hidden")}
onClick={e => this.props.navBarClick(e.target.id)}>
{this.props.title}
</div>
);
}
}
However, the test always fails, because scryRenderedComponentsWithType always returns an empty array, even when I put add calls to jest.dontMock for both App and the file from which NavBarItem is imported. Am I using it wrong, or is what I want just not possible?
Make sure you have imported both react and react-dom to the test, as well as App and NavBarItem components. If we can't see the entire file it's harder to help.
...If that didn't help:
Have you tried other methods? Like:
scryRenderedDOMComponentsWithClass(
component,
NavBarItem)

Checkbox is not `checked` after simulate `change` with enzyme

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.

Resources