How to improve test of react component with jest? - reactjs

I need help for understand how to improve my test ?
I covered branches tests to 100% but can't do statements tests, functions tests and lines tests so how to covered these ?
Here is my code-coverage (provided by jest):
And here is my test:
/**
* Testing our ItemList component
*/
import { shallow } from 'enzyme';
import React from 'react';
import { BootstrapProvider } from 'bootstrap-styled';
import ItemList from '../index';
const renderComponent = (props = {}) => shallow(
<BootstrapProvider>
<ItemList {...props} />
</BootstrapProvider>
);
describe('<ItemList />', () => {
it('should render an <ItemList /> tag', () => {
const renderedComponent = renderComponent();
expect(renderedComponent.find('ItemList').length).toBe(1);
});
});
Any advice are welcome.

Firstly, you can see that the this.props is highlighted and that you have set within the component the props className with a className by default.
You could test the presence of the default className and also trying to set one:
it('should render an <ItemList /> with a className', () => {
const renderedComponent = renderComponent({
className:'className-test'
});
expect(renderedComponent.find('ItemList').hasClass('className-test')).toEqual(true);
});
it('should render an <ItemList /> with a className 'item' by default ', () => {
const renderedComponent = renderComponent();
expect(renderedComponent.find('ItemList').hasClass('item')).toEqual(true);
});
As for the rendering, I suggest you follow the advice given in the comment.

Related

Jest testing of hook onClick calls

This error appears when running this onClick call test with Jest, does anyone know why?
I'm developing an app with React, and I'm new to its testing framework. I'm trying to develop a testing method to check if an onClick button is clicked. I've read that I need a "spy" function to get the information about whether or not it's been called.
The preference is to keep an enzyme and jest test stack.
expect(received).toBe(expected) // Object.is equality
Expected: 1
Received: 0
46 | test("onClick calls props.onClick", () => {
47 | nodes.simulate("click");
> 48 | expect(onClickMock.mock.calls.length).toBe(1);
Since I'm mocking onClick in a way that was to return the value 1
Test
import React from "react";
import Enzyme, { shallow } from "enzyme";
import Adapter from "#wojtekmaj/enzyme-adapter-react-17";
import { expect } from "#jest/globals";
import { findByTestId } from "../../utils/test";
import Accordion from "./accordion.jsx";
Enzyme.configure({ adapter: new Adapter() });
const defaultProps = {
opened: {},
Accordion: {},
};
const setup = (props = {}) =>
shallow(<Accordion {...props} {...defaultProps} />).dive();
describe("Function calls", () => {
let wrapper, nodes, props;
describe("Button", () => {
let onClickMock;
beforeEach(() => {
onClickMock = jest.fn();
props = {onClick: onClickMock};
wrapper = setup(props);
nodes = findByTestId(wrapper, "arrow");
});
test("onClick calls props.onClick", () => {
nodes.simulate("click");
expect(onClickMock.mock.calls.length).toBe(1);
});
});
});
Component
import { useState } from 'react';
import styled from 'styled-components';
import { IoIosArrowForward, IoIosArrowDown } from 'react-icons/io';
import styles from '../../variables';
const Accordion = (props) => {
const [opened, setOpened] = useState(false);
const renderArrow = () => {
if (!opened) return <IoIosArrowForward className="is-size-4" color={styles.primaryBlue} />
return <IoIosArrowDown className="is-size-4" color={styles.primaryBlue} />
}
const renderPanel = () => {
if (opened) return <Panel>{props.children}</Panel>
}
const togglePanel = () => {
setOpened(!opened)
}
return (
<AccordionTile data-testid="accordionTile" className={props.className + " px-6 py-5 is-clickable"}>
<p data-testid="arrow" onClick={togglePanel} className="is-flex is-flex-direction-row is-align-items-flex-start is-justify-content-flex-start is-size-5">
{renderArrow()} {props.title}
</p>
{renderPanel()}
</AccordionTile>
);
}
export default Accordion
Could someone guide me to finish testing this onClick
So you click and check that content appears:
wrapper = setup({
children: <span id="test" />
});
// accordion is collapsed yet
expect(wrapper.find('span#test')).toHaveLength(0);
nodes.simulate('click');
// accordion is expanded; children nodes are rendered
expect(wrapper.find('span#test')).toHaveLength(1);
Also you better switch from shallow() to mount() before you run into issues with useEffect is not called. You will still be able to avoid rendering some nested components by explicitly mocking them:
jest.mock('react-icons/io', () => ({
IoIosArrowForward: (props) => <span {...props} />,
IoIosArrowDown: props => <span {...props} />
}));
And also in way you do it now:
<Accordion {...props} {...defaultProps} />
your defaultProps overwrite props with the same name. To have it working in reasonable way(when defaultProps can be overwritten by props if you want to) it should go in reverse order:
<Accordion {...defaultProps} {...props} />

How to check if a component disappears after click using jest & enzyme?

I have a component,
function TestComponent() {
const [visible, setVisible] = useState(true);
return (
<React.Fragment>
{visible && <Container>
I'm Visible
<button onClick={() => setVisible(false)}>
click to close
</button>
</Container>}
</React.Fragment>
);
}
I'm trying to test that on clicking the button the component should be invisible.
And I have following test case to test that,
test('Random Test', () => {
const randomComponent = shallow(<TestComponent />);
expect(randomComponent.find('Container')).toBeTruthy();
randomComponent.find('button').simulate('click');
expect(randomComponent.find('Container')).toBeFalsy();
});
Doesnt seem to work,
Getting error,
expect(received).toBeFalsy()
Received: {}
Any help would be appreciated?
I have a sneaky suspicion that this isnt the way to check if component is hidden. Would also appreciate if anyone could tell a better way.
Update #1:
expect(randomComponent.render().text()).toContain('I\'m Visible');
randomComponent.find('button').simulate('click');
expect(randomComponent.render().text()).toContain('');
Using the above testcases seem to work. Still looking for a better way.
Since .find() will always return an instance of ShallowWrapper class even if there is no node matched. See source code of .find() and .wrap(). It will NOT return a falsy value (null, undefined), so .toBeFalsy() assertion will always fail.
Use .exists([selector]) => Boolean
Returns whether or not any nodes exist in the wrapper. Or, if a selector is passed in, whether that selector has any matches in the wrapper.
import { shallow } from 'enzyme';
import React from 'react';
import { TestComponent } from './';
describe('68334346', () => {
it('should pass', () => {
const randomComponent = shallow(<TestComponent />);
expect(randomComponent.find('Container').exists()).toBeTruthy();
randomComponent.find('button').simulate('click');
expect(randomComponent.find('Container').exists()).toBeFalsy();
});
});
Use .toHaveLength(number) matcher
import { shallow } from 'enzyme';
import React from 'react';
import { TestComponent } from './';
describe('68334346', () => {
it('should pass', () => {
const randomComponent = shallow(<TestComponent />);
expect(randomComponent.find('Container')).toHaveLength(1);
randomComponent.find('button').simulate('click');
expect(randomComponent.find('Container')).toHaveLength(0);
});
});

How to render elements inside render prop while testing with Enzyme

I have a component which have this structure
return <Element>
{(a, b): React.ReactElement => (
...some elements here
)}
</Element>;
When I'm testing it I'm getting this with debug
<Element>
[function]
</Element>
My question is what is usually the way to get inside this function to check the elements which it returns?
My test
import React from 'react';
import {createShallow} from '#material-ui/core/test-utils';
import Component from './Component';
let shallow;
function setup() {
const props = {};
const wrapper = shallow(<Component {...props} />);
return {
props,
wrapper,
};
}
beforeAll(() => {
shallow = createShallow({ dive: true });
});
describe('Components', () => {
describe('Element', () => {
it('Should render self', () => {
const {wrapper, props} = setup();
console.log(wrapper.debug())
expect(wrapper.exists()).toBeTruthy();
});
});
});
There are two solutions:
Use wrapper.dive() or wrapper.shallow() that would cause your ShallowWrapper to render child of your top-level component. However, I do not recommend using shallow at all as there are many issues with it and you've just experienced one - it does not render everything.
Use mount instead of shallow and you would have your children function rendered by default.

Material UI + Enzyme testing component

I have component in React which I'm trying to test with Jest, unfortunately test do not pass.
The component code:
import React, {Component} from 'react';
import ProductItem from '../ProductItem/ProductItem';
import AppBar from "#material-ui/core/es/AppBar/AppBar";
import Tabs from "#material-ui/core/es/Tabs/Tabs";
import Tab from "#material-ui/core/es/Tab/Tab";
import {connect} from 'react-redux';
class ProductsTabsWidget extends Component {
state = {
value: 0
}
renderTabs = () => {
return this.props.tabs.map((item, index) => {
return item.products.length > 0 ? (<Tab key={index} label={item.title}/>) : false;
})
}
handleChange = (event, value) => {
this.setState({value});
};
renderConentActiveTab = () => {
if (this.props.tabs[this.state.value]) {
return this.props.tabs[this.state.value].products.map((productIndex) => {
return (<ProductItem key={productIndex} {...this.props.products[productIndex]} />);
});
}
}
render() {
let tabs = null;
let content = null;
if (this.props.tabs) {
tabs = this.renderTabs();
content = this.renderConentActiveTab();
}
return (
<div>
<AppBar position="static" color="default">
<Tabs
value={this.state.value}
onChange={this.handleChange}
indicatorColor="primary"
textColor="primary"
centered
scrollButtons="auto"
>
{tabs}
</Tabs>
</AppBar>
<div className="productWidget">
<div className="wrapper">
{content}
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
products: state.product.products,
}
}
export default connect(mapStateToProps)(ProductsTabsWidget);
I have tried to write proper test for this component, the code is below:
import React from 'react';
import {configure, shallow} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import ProductsTabsWidget from "./ProductsTabsWidget";
configure({adapter: new Adapter()});
describe('ProductsTabsWidget - component', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<ProductsTabsWidget/>);
});
it('renders with minimum props without exploding', () => {
wrapper.setProps({
tabs: [],
products:[]
});
expect(wrapper).toHaveLength(1);
});
})
But when I'm running test I am getting error:
Test suite failed to run
F:\PRACA\reactiveShop\node_modules\#material-ui\core\es\AppBar\AppBar.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import _extends from "#babel/runtime/helpers/builtin/extends";
^^^^^^
SyntaxError: Unexpected token import
at new Script (vm.js:51:7)
at Object.<anonymous> (src/components/product/ProductsTabsWidget/ProductsTabsWidget.js:3:15)
I have tried testing with shallow, mount, render but it did not help. What am I missing?
My application is created on create-react-app.
It's something different when you're using #material-ui.
You've to use #material-ui's Built-in API(s). Such as createMount, createShallow, createRender in order to use enzyme's shallow, mount & render.
These APIs are built on top of enzyme, so you can't use enzyme directly for testing #material-ui.
Example of Shallow Rendering with #material-ui
import { createShallow } from '#material-ui/core/test-utils';
describe('<MyComponent />', () => {
let shallow;
before(() => {
shallow = createShallow();
});
it('should work', () => {
const wrapper = shallow(<MyComponent />);
});
});
Reference: Official Docs of #material-ui
Following is a humble attempt to provide a more complete answer from create-react-app and #material-ui perspective.
1. Create setupTests.js directly in src folder and paste the following code.
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() });
2. The following is react stateless component which uses material-ui components.
import React from "react";
import TextField from "#material-ui/core/TextField";
const SearchField = props => (
<TextField InputProps={{ disableUnderline: true }} fullWidth
placeholder={props.placeholder}
onChange={props.onChange}
/>
);
export default SearchField;
Note that in the above component, the component expects parent component to pass the props for placeholder and onChange() event handler
3. Coming to the test case for for the above component we can write either in a way material-ui suggests or in a pure enzyme style. Both will work.
Pure Enzyme Style
import React from "react";
import { mount } from "enzyme";
import TextField from "#material-ui/core/TextField";
import SearchField from "../SearchField";
describe("SearchField Enzyme mount() ", () => {
const fieldProps = {
placeholder: "A placeholder",
onChange: jest.fn()
};
const Composition = props => {
return <SearchField {...fieldProps} />;
};
it("renders a <TextField/> component with expected props", () => {
const wrapper = mount(<Composition />);
expect(wrapper.childAt(0).props().placeholder).toEqual("A placeholder");
expect(wrapper.childAt(0).props().onChange).toBeDefined();
});
it("should trigger onChange on <SearchField/> on key press", () => {
const wrapper = mount(<Composition />);
wrapper.find("input").simulate("change");
expect(fieldProps.onChange).toHaveBeenCalled();
});
it("should render <TextField />", () => {
const wrapper = mount(<Composition />);
expect(wrapper.find(TextField)).toHaveLength(1);
expect(wrapper.find(TextField).props().InputProps.disableUnderline).toBe(
true
);
});
});
Material UI style
import React from "react";
import { createMount } from "#material-ui/core/test-utils";
import TextField from "#material-ui/core/TextField";
import SearchField from "../SearchField";
describe("SearchField", () => {
let mount;
const fieldProps = {
placeholder: "A placeholder",
onChange: jest.fn()
};
beforeEach(() => {
mount = createMount();
});
afterEach(() => {
mount.cleanUp();
});
it("renders a <TextField/> component with expected props", () => {
const wrapper = mount(<SearchField {...fieldProps} />);
expect(wrapper.props().placeholder).toEqual("A placeholder");
expect(wrapper.props().onChange).toBeDefined();
});
it("should trigger onChange on <SearchField/> on key press", () => {
const wrapper = mount(<SearchField {...fieldProps} />);
wrapper.find("input").simulate("change");
expect(fieldProps.onChange).toHaveBeenCalled();
});
});
5. The error you are getting is due to the fact that babel is not getting a chance to process your file. The create-react-app expects you to run tests like yarn run test and not like jest your/test/file.js. If you use latter babel won't be employed.
If you want to use jest to run the file you will have to write a jest.config.js file or configure jest in package.json file to use babel-jest + other babel dependencies to transpile your code before jest tries to execute tests.
I was in the same boat yesterday as I tried to use #material-ui for the first time and came here to get a more complete answer.
Something like this worked for me:
import {createMount} from '#material-ui/core/test-utils';
const WrappedComponent = () =>
<MUIThemeStuffEtc>
<MyComponent />
</MUIThemeStuffEtc>
const render = createMount();
const wrapper = render(<WrappedComponent />);
const state = wrapper.find(MyComponent).instance().wrappedInstance.state

Shallow test React branch Jest and Enzyme

There are three things I want to figure out. For now I am using shallow render. I use Enzyme and Jest.
I am wondering how I can test the branches in my React component. I
would like to test both sides of the if-else statement (? :). And I
do not want to pull it out in an own function.
How can I check if this.props.myFuncFromProps(value) is been called
when the input changes?
What is the best practice to test mapStateToProps and
mapDispatchToProps?
Here is an example of how my component would look like:
import React from 'react';
import MyChildComponent from 'wherever'; // This component is an input field in this example
export class MyComponent extends React.Component {
render() {
const myFunc(value) {
this.props.myFuncFromProps(value);
}
return (
<div>
{ this.props.isTrue ?
<MyChildComponent
value={this.props.value}
onChange={(value) => myFunc(value)}
/>
: null
}
</div>
);
}
}
To test the different states just render your component with the different attributes and make a snapshot (note, that you have to check the first time the snapshot was created). To test event callback, you have to pass a spy function (jest.fn()) into to component and use simulate to call the event, then test that the spy was called.
describe('MyComponent', () => {
describe('with isTrue is true', () => {
let myComponent
let myFuncFromProps
beforeEach(() => {
myFuncFromProps = jest.fn()
myComponent = shallow(
<MyComponent isTrue myFuncFromProps={myFuncFromProps} />
)
})
it('renders correct', () => {
expect(myComponent).matchSnapshot()
})
it('onchange will call myFuncFromProps', () => {
myComponent
.find('MyChildComponent')
.simulate('onChange', 'someValue')
expect(myFuncFromProps).toHaveBeenCalledWith('someValue')
})
})
it('with isTrue is false it renders correct', () => {
const myComponent = shallow(<MyComponent />)
expect(myComponent).matchSnapshot()
})
})

Resources