How to test properties and functions on a React component? - reactjs

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.

Related

useContext values return undefined in testing

I am new to testing and using Enzyme and Jest to write very simple tests. I just want to check whether the component renders or not. However, (I guess) because my component uses useContext hook, test case automatically returns undefined for all values come from the Context.
In component:
const { count, setCount } = useContext(Context);
Test case:
it('should render', () => {
const component = shallow(<MyComponent />);
const wrapper = component.find('myClassName');
expect(wrapper.length).toBe(1);
});
Test Result: Cannot read property 'count' of undefined. I don't know what I am doing wrong. Is there a simple way that always works with useContext and other hooks to test simple things?
I think the problem here is that when you shallow rendering a component, Context will be ignored. So try mounting your component instead of shallow rendering it like so:
import { mount } from "enzyme"; // mount instead of `shallow` here
...
it('should render', () => {
const component = mount(<MyComponent />); // `mount` here as well
const wrapper = component.find('myClassName');
expect(wrapper.length).toBe(1);
});

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

How do I test a method defined within a functional component, that interacts with DOM elements and has no arguments

I have been having trouble getting 100% test coverage on one of my buttons (A React functional components.) Basically when it is clicked, it executes some code and then also calls another method from within this onClick called resetButtons. This method will find all the buttons like it in the app and remove a class. This is a preemptive behavior so that only one button at a time can be active.
So far I have tested the click using .simulate, passing in a mocked domElement. And then test that the domElement.classList.add method is called with 'active'.
Obviously this being a DOM centered operation, I am finding it very difficult to test the resetButtons method that lies within the component. especially considering it doesn't have any methods.
I have tried defining the resetButtons method outside of the component and then exported it so the jest test could import it. However I have been unable to test the method as it seems to want it to be a spy or mock, and not the method itself. (Matcher error: received value must be a mock or spy function
)
Here is the react Functional Component:
import React from 'react';
import PropTypes from 'prop-types';
import classes from './MainButton.module.scss';
const MainButton = (props) => {
const resetButtons = () => {
const elements = document.getElementsByClassName('mainButton');
for (let i = 0; i < elements.length; i += 1) {
elements[i].classList.remove('active');
}
};
const handleClick = (event) => {
if (!event.target.classList.contains('active')) {
resetButtons();
event.target.classList.add('active');
props.setVisualState(props.className.split('-')[0]);
}
};
return (
<button
onClick={handleClick}
type="button"
className={`${classes.mainButton} ${props.className}`}
>
{props.children}
</button>
);
};
MainButton.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
setVisualState: PropTypes.func.isRequired,
};
MainButton.defaultProps = {
children: 'Button',
className: '',
};
export default MainButton;
Here is the Test
import React from 'react';
import { shallow } from 'enzyme';
import MainButton from './MainButton';
describe('MainButton', () => {
const domElement = { classList: { contains: jest.fn(), remove: jest.fn(), add: jest.fn() } };
const setVisualStateMock = jest.fn();
const mainButton = shallow(<MainButton setVisualState={setVisualStateMock} />);
it(' is rendered properly', () => {
expect(mainButton).toMatchSnapshot();
});
describe('when clicked', () => {
beforeEach(() => {
mainButton.find('button').simulate('click', { target: domElement });
});
it('it runs `classlist.add` to assign `active` class', () => {
expect(domElement.classList.add).toHaveBeenCalledWith('active');
});
it('it runs set visual state to update `Allergen` container `state`', () => {
expect(setVisualStateMock).toHaveBeenCalled();
});
});
});
Currently the coverage report is reporting 92% coverage, but the branch is at 50 and the line that is causing the trouble is on line 9 (the elements[i].classList.remove('active'); line.
I know at 90% I should probably just move on but this is something I want to be able to figure out. Feel like getting head around this will make me a better tested.
Hope you guys can help!
Fumbling around in the DOM yourself is an anti-pattern. That's React's job. Instead of manipulating the dom with target.classList.add you should have a state property that holds the status which of your inputs is currently active. Then, while rendering you can say className={isActiveInput ? "active": null}.
Because the state is not specific to your MainButton component you would lift the state up. If you have the state somewhere in the parent you don't have to crudely search for DOM elements by classname and manipulate the dom yourself.
Simply put, the rule of React is: You define how things are supposed to look like, React takes care that your definition becomes reality in the dom. If you manipulate the DOM yourself - you're doing it wrong.
When all of this is done, you will have no problem at all with tests, because all you have to do is provide the proper state and props, which is easy, and check that your callback is triggered onClick.
EDIT: Advanced version would be to use Context, but I'd go with state lifting first.
You should be able to mount multiple MainButtons, click one and expect that the other(s) had domElement.classList.remove called on them.
However, user konqi is right in that React provides better ways of manipulating elements/components.
You could replace this test:
expect(domElement.classList.add).toHaveBeenCalledWith('active');
with a test that checks that the button has (or does not have) the active className (instead of checking that the function was called with the right argument). With that test in place, if you like, you could refactor this in the way that konqi suggests.

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');
});

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

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.

Resources