Mocha, Enzyme: Unit testing custom functions in react component using enzyme - reactjs

I am working on creating unit tests of react components using mocha, enzyme. Below is a sample component.
Foo.js
class Foo extends React.Component {
customFunction=() => {
}
render() {
return (<div className={this.props.name}/>);
}
}
And here is the testing file.
Foo-Test.js
import React from 'react';
import { expect } from 'chai';
import { shallow, mount, render } from 'enzyme';
import Foo from '../src/Foo';
describe("A suite", function() {
it("contains spec with an expectation", function() {
expect(shallow(<Foo />).contains(<div className="foo" />)).to.equal(true);
});
it("contains spec with an expectation", function() {
expect(shallow(<Foo />).is('.foo')).to.equal(true);
});
});
Everything is good. but I didn't understand how to unit test customFunction in Foo.js when we are using enzyme

The best answer to this question really depends on what it is that customFunction is actually doing...
You can call the function like this:
wrapper.instance().customFunction('foo', 'bar');
If it's a function that sets state on the instance itself, and thus affects what the rendered output looks like, you may want to call .update() as well
wrapper.instance().customFunction('foo', 'bar'); // uses setState internally
wrapper.update(); // updates render tree
// do assertions on the rendered output

You can also use the chai plugin to spy on custom functions in you jsx file.
// to use this pluggin add this to the top of your testing file
const chai = require("chai"), spies = require("chai-spies");
chai.use(spies);
import Foo from "./<path to component>/Foo.jsx";
describe("Foo", () => {
it("a call to customFunction will not error", () => {
let spy = chai.spy(Foo.prototype, "customFunciton"); // spy
const wrapper = mount(<Foo/>);
wrapper.setProps({bar: "baz"}); // manipulate you component in some way
expect(spy).to.have.been.called.once();
});
});
#leland-richardson is right, it depends on what your test is doing. Understanding that will help you compose new ways to manipulate your component and thus make assertions.
Another example testing a function that updates your components state.
it("function will assert new state", () => {
const wrapper = shallow(<Foo {...props}/>);
wrapper.instance.customFunction(); // call custom function
wrapper.update();
expect(wrapper.state("bar")).to.equal("new-state");
});
Chai-spies also has a handful of chainable getters that make testing custom functions much easier. Please see the docs for a more in-depth explanation.

Related

How to make event handler function mock in react and react testing library

I would like to know how to make event handler function be mock in react and react testing library.
For example, in the following source code, when a button is clicked, a function handleClick is fired.
import * as React from 'react';
import { goToSecondPage } from './PageTransitionUtuil';
const IndexPage = () => {
const handleClick = (e) => {
console.log(e);
goToSecondpage();
};
return (
<button onClick={(e) => handleClick(e)} />
)
}
I would like to know
How to create a mock function in react testing library and jest.
How to set the mock function into the dom event handler in a test case.
As I said in comments it's more preferred to mock external api and functions, here is a way that shows you how to mock using jest.mock factory function.
jest.mock("./PageTransitionUtil", () => ({
goToSecondPage: jest.fn(),
//Add other methods you want to mock here
}));
you can read more about it in the official jest documents
Do an import * as of your utility file that you can then mock individual functions of for testing.
import * as utils from "./PageTransitionUtil";
jest.mock("./PageTransitionUtil");
utils.goToSecondPage = jest.fn();
From here the goToSecondPage is a mock function that you can do assertions on, i.e. expect(utils.goToSecondPage).toHaveBeenCalled().

react-test-renderer's create() vs. #testing-library/react's render()

I'm new to React and confused about all the testing libraries. I got my test code to work but it seems redundant to have to call create() from react-test-renderer in order to use its toMatchSnapshot() and have to call render() from #testing-library/react in order to use its assertions such as getByLabelText().
import {render} from '#testing-library/react';
import {act, create} from 'react-test-renderer';
it('renders a login screen', () => {
let mockInitialState: AppState = {
auth: initialAuthState
};
let component = <Root initialState={mockInitialState}/>;
let tree = null;
act(() => {
tree = create(component);
});
expect(tree).toMatchSnapshot();
const {getByLabelText, getByText} = render(component);
expect(getByLabelText(/Email Address.*/));
expect(getByLabelText(/Password*/));
expect(getByText('Sign in'));
});
As a newbie, it's hard for me to understand the difference between all these React libraries. But I'm thinking there must be a simpler way.
How can I simplify my test code so I only have to call one thing that renders the component so that I can do snapshot testing and more specific assertions?
I got the answer from Ziad Saab at Codementor.io:
create() allows you test against the virtual DOM (i.e. the "React DOM")
render() comes from react testing library and renders your tree but also allows you to have all the get*() assertions. It allows you to test against the DOM.
Here's how the code can be simplified:
it('renders a login screen', () => {
let mockInitialState: AppState = {
auth: initialAuthState
};
const {container, getByLabelText, getByText} = render(<Root initialState={mockInitialState}/>);
expect(container.firstChild).toMatchSnapshot();
expect(getByLabelText(/Email Address.*/));
expect(getByLabelText(/Password*/));
expect(getByText('Sign in'));
});
Ziad let me know that there was no reason to have act(), it was something to work around a bug in create(). Now that the code doesn't used create() there is no need for act().
As a result, my snapshot now contains class instead of className because class is what's in the actual HTML DOM whereas className is its equivalent in React's "Virtual DOM".
(Before) Snapshot with create() based on React's Virtual DOM:
className="MuiBox-root MuiBox-root-256"
(After) Snapshot with render() based on HTML DOM:
class="MuiBox-root MuiBox-root-256"
If you're using Create React App then I'd stick with react-testing-library since it comes with it.
Instead of container, you can also use asFragment for snapshot testing.
const {container} = render(<Root initialState={mockInitialState}/>);
expect(asFragment).toMatchSnapshot();

Enzyme: How can I test a component with DOM side-effect?

Say I have a component like so -
// #flow
import React, { PureComponent } from 'react';
export default class ReplaceLink extends Component {
containerRef = React.createRef();
componentDidMount() {
const links =
Array.from(this.containerRef.current.getElementsByTagName('a'));
links.forEach(a => a.setAttribute('href', 'dummylink'));
}
render = () => <div ref={this.containerRef}>{this.props.children}</div>;
}
which replaces href of links placed within it. But even when doing full dom rendering in enzyme, when I do a wrapper.debug() to see the result of the render, I still see original links only.
I've tried doing a force wrapper.update and using setTimeouts, but it just doesn't reflect the expected link.
One of reasons why direct DOM access is discouraged in React is that it makes testing more complicated.
The component can be rendered with skipped componentDidMount:
const wrapper = shallow(<ReplaceLink/>, { disableLifecycleMethods: true })
Then a ref can be mocked and componentDidMount can be called manually:
const setAttribute = jest.fn();
const getElementsByTagName = jest.fn().mockImplementation(() => [{ setAttribute }]);
wrapper.instance().containerRef.current = { getElementsByTagName };
wrapper.instance().componentDidMount();
Then stubbed DOM functions can be asserted that they were called.
Found the best way to test something like this is through the getDOMNode method.
First, make sure to use mount to render the wrapper, so we have a simulated DOM environment to query against.
Next, use wrapper.getDOMNode() to get the underlying DOM node.
Any changes made during the lifecycle methods to the underlying DOM will be reflected in this DOM reference.
Use .querySelector, or <insert-dom-query-method> to make assertions.
const wrapper = mount(
<ReplaceLink>
Google
</ReplaceLink>
);
const linkTags = wrapper.getDOMNode().querySelectorAll('a');
linkTags.forEach(tag => {
expect(tag.getAttribute('href')).toBe('dummy');
});

Why doesn't my React onChange method match my arrow function in enzymes containsAllMatchingElements test

I have written a test case which is trying to test if react is rendering all my elements correctly.
Code that is being tested:
...
eventDateChange(moment, dateType) {
const {handleEventChange} = this.props;
let {event} = this.state;
event[dateType] = moment.format("YYYY-MM-DD HH:mm");
this.setState({event});
handleEventChange(event);
};
render() {
return (
<div className="event-input">
<DateTime
onChange={moment => this.eventDateChange(moment,'startDate')}
inputProps={{placeholder: 'From:'}}
dateFormat="YYYY-MM-DD"/>
</div>
)
}
...
Test code:
import React from "react";
import {expect} from "chai";
import {shallow} from "enzyme";
import EventInput from "../../../blog/event/EventInput.jsx";
import DateTime from "react-datetime";
describe('EventInput', () => {
it('is rendering an calendar icon', () => {
const wrapper = shallow(<EventInput/>);
expect(wrapper.containsAllMatchingElements([
<DateTime
onChange={moment => wrapper.instance.eventDateChange(moment,'startDate')}
inputProps={{placeholder: 'From:'}}
dateFormat="YYYY-MM-DD"/>
])).to.equal(true);
});
});
The problem is that my onChange method is failing the tests. If I remove the onChange method from the code and the test, the test is succeeding.
As you can see I was using Mocha, Chai, Enzyme in the tests.
From what I can see all the props are the same except for the onChange where I can't use this in the test and need to change it to the instance.
Looking at the implementation of how enzyme compares nodes (https://github.com/airbnb/enzyme/blob/d34630e9c3e07ca7983d37695d5668377f94a793/src/Utils.js#L102), it looks like enzyme requires props of type "function" to exactly match when doing this comparison (right[prop] === left[prop]) and will return false if this condition is not met.
Your lambda functions are not identical, so the === comparison will fail. Whenever you use lambda, you are creating a new anonymous function, so even if the parameters and body are the same between two lambda declarations, they are actually two separate function instances.
To get around this problem, you could either 1) create a reference to the exact function instance used in your component so that your test can reference it, or 2) use a different enzyme API here. I would suggest #2. It seems like you are overtesting a bit by checking all property values, and you could use something like this instead:
it('is rendering an calendar icon', () => {
const wrapper = shallow(<EventInput/>);
expect(wrapper.matchesElement(
<div>
<DateTime />
</div>
)).to.equal(true);
});
If you want to test individual props on the DateTime, you could additionally do something like: expect(wrapper.find("DateTime").props().dateFormat).to.equal("YYYY-MM-DD").

How to test properties and functions on a React component?

I've tried everything with enzyme, however, I can't find the correct way of testing these properties below. Keep in mind that this component is wrapped in a dummy Provider component so that I can pass the necessary props (i.e. Store) down for mounting purposes.
1) After mounting, a property is set on the instance (e.g. this.property)
2) An event listener has been added
3) On the event listener, someFunction is being called
class SampleComponent extends Component {
componentDidMount() {
this.property = 'property';
window.addEventListener('scroll', this.someFunction, true);
}
someFunction = () => {
return 'hello';
};
render() {
return <h1>Sample</h1>;
}
}
export default EvalueeExposureList;
Ok, I have updated my answer based on discussion with OP. The component under test has a redux provider and connected component as child therefore we are opting for the usage of enzymes shallow API.
In regards to tracking and testing the addEventListener you can use the sinon library to create a spy, which temporarily "replaces" the window.addEventListener. This grants you access to the call count as well as the arguments it was called with.
Using enzyme and mocha I created the following tests which were passing for me. The first two test covers all your cases above and for good measure I added another on how to test the output of the someFunction.
import React from 'react';
import { expect } from 'chai';
import sinon from 'sinon';
import { shallow } from 'enzyme';
// Under test.
import SampleComponent from './SampleComponent';
describe('SampleComponent', () => {
let addEventListenerSpy;
beforeEach(() => {
// This replaces the window.addEventListener with our spy.
addEventListenerSpy = sinon.spy(window, 'addEventListener');
});
afterEach(() => {
// Restore the original function.
window.addEventListener.restore();
});
// This asserts your No 1.
it(`should set the property`, () => {
const wrapper = shallow(<SampleComponent />);
wrapper.instance().componentDidMount(); // call it manually
expect(wrapper.instance().property).equal('property');
});
// This asserts your No 2 and No 3. We know that by having
// passed the someFunction as an argument to the event listener
// we can trust that it is called. There is no need for us
// to test the addEventListener API itself.
it(`should add a "scroll" event listener`, () => {
const wrapper = shallow(<SampleComponent />);
wrapper.instance().componentDidMount(); // call it manually
expect(addEventListenerSpy.callCount).equal(1);
expect(addEventListenerSpy.args[0][0]).equal('scroll');
expect(addEventListenerSpy.args[0][1]).equal(wrapper.instance().someFunction);
expect(addEventListenerSpy.args[0][2]).true;
});
it(`should return the expected output for the someFunction`, () => {
const wrapper = mount(<SampleComponent />);
expect(wrapper.instance().someFunction()).equal('hello');
});
});
It may be worth noting that I run my tests on node, but I have a jsdom setup in my mocha configuration, which is probably the candidate responsible for creating the window.addEventListener in for use in my test environment. Are you running your tests via the browser or node? If node you may need to do something similar to me.

Resources