ReactWrapper can only wrap valid elements (material-ui - enzyme) - reactjs

I'm writing a test for a component to test one of its functions, but I am getting an error: ShallowWrapper can only wrap valid elements
The component file is as follows - TextInput.js:
/* eslint-disable react/require-default-props */
import React from 'react'
import PropTypes from 'prop-types'
import { InputLabel, TextField } from '#material-ui/core'
const TextInput = ({
name, label, onChange, placeholder, value, error, optional = false, isDisable = false, t,
}) => (
<>
{label ? (
<InputLabel htmlFor={name} className="default-style_label">
{label}
{' '}
{optional && <span className="optional">{`(${t('application.optional')})`}</span>}
</InputLabel>
) : ''}
<TextField
type="text"
name={name}
placeholder={placeholder}
value={value}
// isRequired={isRequired}
disabled={isDisable}
onChange={onChange}
error={error && true}
helperText={error}
variant="outlined"
className="default-style_input"
/>
</>
)
TextInput.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string,
onChange: PropTypes.func.isRequired,
placeholder: PropTypes.string,
value: PropTypes.string,
error: PropTypes.string,
optional: PropTypes.bool,
isDisable: PropTypes.bool,
t: PropTypes.func,
}
export default TextInput
The test file
/* eslint-disable no-undef */
import React from 'react'
import { shallow, mount } from 'enzyme'
import TextInput from '../TextInput'
function createTestProps(props) {
return {
// common props
name: 'test',
label: 'foo',
value: 'bar',
onChange: jest.fn(),
// allow to override common props
...props,
}
}
describe('rendering', () => {
describe('<TextInput>', () => {
let wrapper
let instance
beforeEach(() => {
const props = createTestProps()
wrapper = mount(shallow(<TextInput {...props} />)).get(0)
instance = wrapper.instance()
})
afterEach(() => {
jest.clearAllMocks()
})
it('should be rendered', () => {
const content = wrapper.find('input').at(1)
console.debug(content.debug())
console.log(instance)
expect(content.value).toBe('bar')
})
})
})
The problem is that my tests fail when remove mount from
wrapper = mount(shallow(<TextInput {...props} />)).get(0)
with a Compared values have no visual difference
Any ideas why this is happening would be much appreciated!

You should use either mount or shallow to render your component based on your use case.
use mount if you want to render your component where all children would be rendered to the last leaf node.
use shallow if you need a level deep rendering of your component.
Note: in most cases, you should use shallow rendering as mount takes a long time.

You should use shallow or mount, not both of them.
wrapper = mount(<TextInput {...props} />);
const content = wrapper.find('input');
expect(content.value).toBe('bar');

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.
Here's my test file
/* eslint-disable no-undef */
import React from 'react'
import { createMount } from '#material-ui/core/test-utils'
import TextField from '#material-ui/core/TextField'
import TextInput from '../TextInput'
function createTestProps(props) {
return {
// common props
name: 'test',
label: 'foo',
value: 'bar',
onChange: jest.fn(),
// allow to override common props
...props,
}
}
describe('rendering', () => {
describe('<TextInput>', () => {
let mount
let props
beforeEach(() => {
mount = createMount()
props = createTestProps()
})
afterEach(() => {
mount.cleanUp()
})
it('renders a <TextField/> component with expected props', () => {
const wrapper = mount(<TextInput {...props} />)
expect(wrapper.props().name).toEqual('test')
expect(wrapper.props().onChange).toBeDefined()
})
it('should trigger onChange on <TextField/> on key press', () => {
const wrapper = mount(<TextInput {...props} />)
wrapper.find('input').simulate('change')
expect(props.onChange).toHaveBeenCalled()
})
})
})
I found the solution here
Material UI + Enzyme testing component

Related

Using a variable in enzyme wrapper find method

Clunking through learning testing with jest + enzyme. I have an array, OptionsArray, with some options that get mapped to buttons in a component. I figured that in the testing suite for the component, I could just do
import React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import { OptionsArray } from './ConfigOptions';
import Foo from './Foo';
describe('Foo', () => {
let wrapper: ShallowWrapper;
const numberOfOptions = OptionsArray.length;
beforeEach(() => (wrapper = shallow(<Foo />)));
it('renders exactly one Button Item for each option', () => {
/* eslint-disable-next-line testing-library/no-debugging-utils */
console.log(wrapper.debug());
OptionsArray.forEach((option) => {
console.log(option.value);
});
OptionsArray.forEach((option) => {
expect(wrapper.find(option.value)).toHaveLength(1);
});
});
});
I see the options fine in the console output, but then I get:
Foo › renders exactly one Button Item for each option
expect(received).toHaveLength(expected)
Expected length: 1
Received length: 0
So I'm guessing that I'm passing the variable to find incorrectly? Is there a better way to do this?
Adding component Foo:
/* Foo.tsx */
import React, { useState } from 'react';
import { Button, ListGroup } from 'react-bootstrap';
import { OptionsArray } from './ConfigOptions';
import './Foo.scss';
const Foo: React.FC<> = () => {
const [options, setOptions] = useState(OptionsArray);
return (
<div className="Foo">
<ListGroup>
{OptionsArray.map((option, i) => (
<ListGroup.Item key={i}>
<Button
id={i.toString()}
value={option.value}
onClick={(e) => handleClick(e.currentTarget.id)}
variant={option.isSet ? 'primary' : 'outline-primary'}
>
{option.value}
</Button>
{option.content}
</ListGroup.Item>
))}
</ListGroup>
</div>
);
};
export default Foo;
And the OptionsArray:
import React from 'react';
export const OptionsArray = [
{
value: 'OptionA',
content: (
<React.Fragment>
<br />
<p>Here is a description of OptionA.</p>
</React.Fragment>
),
isSet: false,
},
{
value: 'OptionB',
content: (
<React.Fragment>
<br />
<p>Here is a description of OptionB.</p>
</React.Fragment>
),
isSet: false,
},
];
I figured it out. As usual, just a misunderstanding on my part. I was trying to use find to get the Button components by text, but this isn't how find works. Instead, I needed to use the findWhere method and a predicate to hunt down the exact components I was looking for. Here was my solution:
import React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import { OptionsArray } from './ConfigOptions';
import Foo from './Foo';
describe('Foo', () => {
let wrapper: ShallowWrapper;
const numberOfOptions = OptionsArray.length;
beforeEach(() => (wrapper = shallow(<Foo />)));
it('renders exactly one Button Item for each option', () => {
OptionsArray.forEach((option) => {
expect(wrapper.find({ value: option.value })).toHaveLength(1);
const ButtonWithText = wrapper.findWhere((node) => {
return node.name() === 'Button' && node.text() === option.value;
});
expect(ButtonWithText ).toHaveLength(1);
});
});
});

Test failed of a Component in react using typescript

When ever I run the test it fails. I don't know what mistake I am making.
How can I test those if statements and the child component. I am using jest and enzyme for react tests.
This is my test file:
import React from "react";
import { shallow } from "enzyme";
import LaunchContainer from "./index";
import Launch from "./Launch";
describe("render LaunchContainer component", () => {
let container: any;
beforeEach(() => { container = shallow(<LaunchContainer setid={()=>{}} setsuccess={()=>{}} />) });
it("should render LaunchContainer component", () => {
expect(container.containsMatchingElement(<Launch setsuccess={()=>{}} setid={()=>{}} data={{}}/>)).toEqual(true);
});
})
The parent component for which the test is used:
import React from "react";
import { useLaunchesQuery } from "../../generated/graphql";
import Launch from './Launch';
interface Props {
setid: any;
setsuccess: any;
}
const LaunchContainer: React.FC<Props> = ({setid, setsuccess}) => {
const {data, loading, error} = useLaunchesQuery();
if (loading) {
return <div>loading...</div>
}
if (error || !data) {
return <div>error</div>
}
return <Launch setid={setid} data={data} setsuccess={setsuccess} />
}
export default LaunchContainer;
The child component to be added in test:
import React from "react";
import { LaunchesQuery } from "../../generated/graphql";
import './style.css';
interface Props {
data: LaunchesQuery;
setid: any;
setsuccess: any;
}
const Launch: React.FC<Props> = ({setid, data, setsuccess}) => {
return (
<div className="launches">
<h3>All Space X Launches</h3>
<ol className="LaunchesOL">
{!!data.launches && data.launches.map((launch, i) => !!launch &&
<li key={i} className="LaunchesItem" onClick={() => {
setid(launch.flight_number?.toString())
setsuccess(JSON.stringify(launch.launch_success))
}}>
{launch.mission_name} - {launch.launch_year} (<span className={launch.launch_success? "LaunchDetailsSuccess": launch.launch_success===null? "": "LaunchDetailsFailed"}>{JSON.stringify(launch.launch_success)}</span>)
</li>
)}
</ol>
</div>
);
}
export default Launch;
To test those if statements you should mock your useLaunchesQuery to return the loading, error and data values that hit your if statements. You can use mockImplementationOnce or mockReturnValueOnce. For example you could write
import { useLaunchesQuery } from "../../generated/graphql";
/**
* You mock your entire file /generated/graphql so it returns
* an object containing the mock of useLaunchesQuery.
* Note that every members in this file and not specified in this mock
* will not be usable.
*/
jest.mock('../../generated/graphql', () => ({
useLaunchesQuery: jest.fn(() => ({
loading: false,
error: false,
data: [/**Whatever your mocked data is like */]
}))
}))
const setid = jest.fn();
const setsuccess = jest.fn();
/**
* A good practice is to make a setup function that returns
* your tested component so so you can call it in every test
*/
function setup(props?: any) { // type of your component's props
// You pass mock functions declared in the upper scope so you can
// access it later to assert that they have been called the way they should
return <LaunchContainer setid={setid} setsuccess={setsuccess} {...props} />
// Spread the props passed in parameters so you overide with whatever you want
}
describe("render LaunchContainer component", () => {
it('should show loading indicator', () => {
/**
* Here I use mockReturnValueOnce so useLaunchesQuery is mocked to return
* this value only for the next call of it
*/
(useLaunchesQuery as jest.Mock).mockReturnValueOnce({ loading: true, error: false, data: [] })
/** Note that shallow won't render any child of LaunchContainer other than basic JSX tags (div, span, etc)
* So you better use mount instead
*/
const container = mount(setup()); // here i could pass a value for setid or setsuccess
// Put an id on your loading indicator first
expect(container.find('#loading-indicator').exists()).toBeTruthy()
/** The logic for the other if statement remains the same */
})
}

How to unit test conditional render of deconstructed props?

I want to test the existence of the Typography element from Material-UI when the bio prop is true. However, I'm receiving an error that the element is not being received. Please advise, thank you.
MyComponent.js
const MyComponent = (props) => {
const {
classes,
profile: { handle, createdAt, profileImage, bio, website, location}
} = props;
return (
{bio && <Typography variant='body2'>{bio}</Typography>}
)
MyComponent.propTypes = {
profile: PropTypes.object.isRequired,
classes: PropTypes.object.isRequired
}
}
export default withStyles(styles)(MyComponent)
MyComponent.test.js
describe('<MyComponent />', () => {
let shallow;
let wrapper;
const myProps = {
profile: {
bio: 'this is a bio'
}
}
beforeEach(() => {
shallow = createShallow();
wrapper = shallow(<MyComponent {...myProps} />);
})
it('should render a Typography element', () => {
console.log(wrapper.debug(MyComponent));
expect(wrapper.dive().find(Typography)).toHaveLength(1);
})
})
Error
expect(received).toHaveLength(expected)
Expected length: 1
Received length: 0
Received object: {}
console.error node_modules/#material-ui/styles/mergeClasses/mergeClasses.js:36
Material-UI: the key `bio` provided to the classes prop is not implemented in MyComponent.
You can only override one of the following: paper,profile.
console.log src/components/MyComponent/MyComponent.test.js:36
<MyComponent classes={{...}} profile={{...}} />

React Jest Test coverage showing untested line (default props)

I am running a test on the simple button component. All works fine, however, in the test coverage I am getting an untested line (21: onClick: () => { return true }). This line points to the default props function. How can I make sure that I have covered the unit test for this line?
My component:
import React from 'react';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
export const Button = (props) => {
return (
<Link
to={props.link}
className={props.classes}
onClick={props.onClick}
>
{props.title}
</Link>
);
};
Button.defaultProps = {
link: '/',
title: 'Home',
classes: 'btn btn--primary',
onClick: () => { return true }
}
Button.propTypes = {
link: PropTypes.string,
title: PropTypes.string,
classes: PropTypes.string,
onClick: PropTypes.func
};
export default Button;
My test:
import React from 'react';
import { shallow } from 'enzyme';
import { Button } from '../../../components/partials/Button';
test('should render button', () => {
const wrapper = shallow(<Button link='/test' title='Home' classes='btn btn--primary' onClick={ () => { return true }} />);
expect(wrapper).toMatchSnapshot();
const props = wrapper.props();
expect(props.to).toBe('/test');
expect(props.className).toBe('btn btn--primary');
expect(props.onClick).toBe(props.onClick);
expect(props.children).toBe('Home');
});
test('should have default onClick', () => {
expect(Button.defaultProps.onClick).toBeDefined();
});
You need to run the onClick function in order for it to count for coverage. So, make a test that simulates a click event on the button, which will trigger its onClick function:
it('does something when clicked', () => {
const wrapper = shallow(<Button link='/test' title='Home' classes='btn btn--primary' onClick={ () => { return true }} />);
wrapper.simulate('click');
expect(...);
});

How to test functions in component with enzyme, jest, recompose, react

Ok, so I'm a little stumped on how to test my component's functionality using enzyme/jest. Still learning how to test my components - I can write simple tests but now I need to make them more complex.
I'd like to know the best way to test that my component's functions are being called properly and that they update state props as they're supposed to. What I'm finding is tricky is that my functions and state all live in my component's props.
If I need to use a spy I'd like to preferably know how to with Jest, but if a dep like Sinon or Jasmine is better suited for the job I'm open to it (just let me know why so I can better understand).
As an example, I have a UserDetails component
const UserDetails = ({
userInfo,
onUserInfoUpdate,
className,
error,
title,
primaryBtnTitle,
submit,
secondaryBtnTitle,
secondaryBtnFunc,
...props
}) => (
<div className={className}>
<div className="user-details-body">
<Section title="User details">
<TextInput
className="firstName"
caption="First Name"
value={userInfo.first}
onChange={onUserInfoUpdate('first')}
name="first-name"
min="1"
max="30"
autoComplete="first-name"
/>
<TextInput
className="lastName"
caption="Last Name"
value={userInfo.last}
onChange={onUserInfoUpdate('last')}
name="last-name"
min="1"
max="30"
autoComplete="last-name"
/>
</Section>
</div>
<div className="errorBar">
{error && <Alert type="danger">{error}</Alert>}
</div>
<ActionBar>
<ButtonGroup>
<Button type="secondary" onClick={secondaryBtnFunc}>
{secondaryBtnTitle}
</Button>
<Button type="primary" onClick={submit}>
{primaryBtnTitle}
</Button>
</ButtonGroup>
</ActionBar>
</div>
TextInput consists of:
<label className={className}>
{Boolean(caption) && <Caption>{caption}</Caption>}
<div className="innerContainer">
<input value={value} onChange={updateValue} type={type} {...rest} />
</div>
</label>
Here is example code of my index.js file that composes my withState and withHandlers to my Component:
import UserDetails from './UserDetails'
import { withState, withHandlers, compose } from 'recompose'
export default compose(
withState('error', 'updateError', ''),
withState('userInfo', 'updateUserInfo', {
first: '',
last: '',
}),
withHandlers({
onUserInfoUpdate: ({ userInfo, updateUserInfo }) => key => e => {
e.preventDefault()
updateCardInfo({
...cardInfo,
[key]: e.target.value,
})
},
submit: ({ userInfo, submitUserInfo }) => key => e => {
e.preventDefault()
submitUserInfo(userInfo)
//submitUserInfo is a graphQL mutation
})
}
})
)
So far, my test file looks like this:
import React from 'react'
import { mount } from 'enzyme'
import UserDetails from './'
import BareUserDetails from './UserDetails'
describe('UserDetails handlers', () => {
let tree, bareTree
beforeEach(() => {
tree = mount(
<ThemeProvider theme={theme}>
<UserDetails />
</ThemeProvider>
)
bareTree = tree.find(BareUserDetails)
})
it('finds BareUserDetails props', () => {
console.log(bareTree.props())
console.log(bareTree.props().userInfo)
console.log(bareTree.find('label.firstName').find('input').props())
})
})
the console logs return me the right information in regards what I expect to see when I call on them:
//console.log(bareTree.props())
{ error: '',
updateError: [Function],
userInfo: { first: '', last: '' },
updateUserInfo: [Function],
onUserInfoUpdate: [Function] }
//console.log(bareTree.props().userInfo)
{ first: '', last: '' }
//console.log(bareTree.find('label.firstName').find('input).props()
{ value: '',
onChange: [Function],
type: 'text',
name: 'first-name',
min: '1',
max: '30',
autoComplete: 'first-name' }
Now the question is how I can use them, and the best way. Do I even use my functions or do I just check to see that onChange was called?
UPDATE (sort of)
I've tried this and I get the following:
it('Input element updates userInfo with name onChange in FirstName input', () => {
const firstNameInput = bareTree.find('label.firstName').find('input)
ccNameInput.simulate('change', {target: {value: 'John'}})
expect(ccNameInput.prop('onChange')).toHaveBeenCalled()
})
In my terminal I get:
expect(jest.fn())[.not].toHaveBeenCalled()
jest.fn() value must be a mock function or spy.
Received:
function: [Function anonymous]
If I try and create a spyOn with Jest, however, I get an error that it cannot read the function of 'undefined'.
I've tried spy = jest.spyOn(UserDetails.prototypes, 'onUpdateUserInfo') and spy = jest.spyOn(BareUserDetails.prototypes, 'onUpdateUserInfo') and they both throw the error.
I believe you should probably test the dumb component (UserDetails) and HOC separately. For the dumb component you want to render the component with shallow and inject the props. To mock the onUserInfoUpdate, you need to do const onUserInfoUpdate = jest.fn();
You want something along the lines of ....
import React from 'react'
import { shallow } from 'enzyme'
import UserDetails from './UserDetails'
const onUserInfoUpdate = jest.fn(); // spy
const props = {
onUserInfoUpdate,
// list all your other props and assign them mock values
};
describe('UserDetails', () => {
let tree;
beforeAll(() => {
tree = shallow(<UserDetails {...props} />)
});
it('should invoke the onUserInfoUpdate method', () => {
const firstNameInput = tree.find('label.firstName').find('input');
firstNameInput.simulate('change', { target: { value: 'John' } });
expect(onUserInfoUpdate).toHaveBeenCalledWith('first');
});
});

Resources