How to unit test conditional render of deconstructed props? - reactjs

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={{...}} />

Related

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

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

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

value must be a mock or spy function when using jest.fn

Getting this error
Matcher error: received value must be a mock or spy function
Received has type: object
Received has value: {}
However, i think i shouldn't be getting this error because im using jest.fn. So im mocking the function.
describe('Should simulate button click', ()=> {
it('should simulate button click', () => {
// add the name of the prop, which in this case ites called onItemAdded prop,
// then use jest.fn()
const wrapper = shallow(<TodoAddItem onItemAdded={() => jest.fn()}/>)
// console.log('props',wrapper.find('button').props());
wrapper.find('button').simulate('click');
expect(wrapper).toHaveBeenCalled(); // error happens when this executes
})
})
todo-add-item.js
import React, { Component } from 'react';
import './todo-add-item.css';
export default class TodoAddItem extends Component {
render() {
return (
<div className="todo-add-item">
<button
className="test-button btn btn-outline-secondary float-left"
onClick={() => this.props.onItemAdded('Hello world')}>
Add Item
</button>
</div>
);
}
}
app.js (using the component in this file)
import React, { Component } from 'react';
import AppHeader from '../app-header';
import SearchPanel from '../search-panel';
import TodoList from '../todo-list';
import ItemStatusFilter from '../item-status-filter';
import TodoAddItem from '../todo-add-item';
import './app.css';
export default class App extends Component {
constructor() {
super();
this.createTodoItem = (label) => {
return {
label,
important: false,
done: false,
id: this.maxId++
}
};
this.maxId = 100;
this.state = {
todoData: [
this.createTodoItem('Drink Coffee'),
this.createTodoItem('Make Awesome App'),
this.createTodoItem('Have a lunch')
]
};
this.deleteItem = (id) => {
this.setState(({ todoData }) => {
const idx = todoData.findIndex((el) => el.id === id);
const newArray = [
...todoData.slice(0, idx),
...todoData.slice(idx + 1)
];
return {
todoData: newArray
};
});
};
this.addItem = (text) => {
const newItem = this.createTodoItem(text);
this.setState(({ todoData }) => {
const newArray = [
...todoData,
newItem
];
return {
todoData: newArray
};
});
};
this.onToggleImportant = (id) => {
console.log('toggle important', id);
};
this.onToggleDone = (id) => {
console.log('toggle done', id);
};
};
render() {
return (
<div className="todo-app">
<AppHeader toDo={ 1 } done={ 3 } />
<div className="top-panel d-flex">
<SearchPanel />
<ItemStatusFilter />
</div>
<TodoList
todos={ this.state.todoData }
onDeleted={ this.deleteItem }
onToggleImportant={ this.onToggleImportant }
onToggleDone={ this.onToggleDone } />
<TodoAddItem onItemAdded={ this.addItem } />
</div>
);
};
};
I'm not 100% sure, but I believe you should do something like this:
describe('should simulate button click', () => {
it('should simulate button click', () => {
const mockedFunction = jest.fn();
const wrapper = shallow(<TodoAddItem onItemAdded={ mockedFunction } />);
wrapper.find('button').simulate('click');
expect(mockedFunction).toHaveBeenCalled();
});
});
You are testing if the onItemAdded function gets called when you click the <TodoAddItem /> component. So you have to mock it first using jest.fn and then check if the mocked function got called after you simulated the click.
For me works replacing the next one:
const setCategories = () => jest.fn();
With this one:
const setCategories = jest.fn();
I suppose that you should to set just jest.fn or jest.fn() in your code.

How to read the children in nodes when testing uisng enzymes

I have a component and I want to test the click method. I am using shallow but my test is failing as it cannot find the button and hence it`s click method. What is wrong with my code?
interface IProps {
label: string;
className: string;
onClick: () => void;
}
export const NewButton: React.StatelessComponent<IProps> = props => {
return (
<Button type="button" className={props.className} onClick={props.onClick}>
{props.label}
</Button>
);
};
import { shallow } from 'enzyme';
import * as React from 'react';
import { NewButton } from "../Buttons";
describe('<NewButton />', () => {
describe('onClick()', () => {
const props = {
className: "buttonSubmit",
label: "submit",
onClick: () => {},
}
test('successfully calls the onClick handler', () => {
const mockOnClick = jest.fn();
const wrapper = shallow(
<NewButton {...props} />
);
const button = wrapper.find('submit').dive();
expect(button.exists()).toEqual(true)
button.simulate('click');
expect(mockOnClick.mock.calls.length).toBe(1);
});
});
});
Since you are using shallow method, it will only render the component that we are testing. It does not render child components. So you should try to find the Button component.
const button = wrapper.find('Button');
After that you should mock the props.onClick event handler passed as props to NewButton component.
const props = {
className: "buttonSubmit",
label: "submit",
onClick: jest.fn(),
}
So you can use
describe('<NewButton />', () => {
describe('onClick()', () => {
const props = {
className: "buttonSubmit",
label: "submit",
onClick: jest.fn(),
}
test('successfully calls the onClick handler', () => {
const wrapper = shallow(
<NewButton {...props} />
);
const button = wrapper.find('Button');
expect(button.exists()).toEqual(true)
button.simulate('click');
// Since we passed "onClick" as props
// we expect it to be called when
// button is clicked
// expect(props.onClick).toBeCalled();
expect(props.onClick.mock.calls.length).toBe(1);
});
});
});

wrapper.find is not a function

I am trying test a component in React w/ TypeScript using Jest and Enzyme.
My test is as follows:
import * as React from 'react';
import { shallow } from 'enzyme';
import * as sinon from 'sinon';
import { ButtonGroup } from '../../../src/components';
describe('.ButtonGroup', () => {
it('should render', () => {
const { wrapper } = setup({});
expect(wrapper.exists()).toBe(true);
});
it('should call the rightHandler handler on click', () => {
const onClickHandler = sinon.spy();
const wrapper = setup({ rightHandler: onClickHandler });
wrapper.find('a').simulate('click');
expect(onClickHandler).toHaveBeenCalled();
});
});
const setup = propOverrides => {
const props = Object.assign({ leftBtn: MOCK_LEFT, rightBtn: MOCK_RIGHT }, propOverrides);
const wrapper = shallow(<ButtonGroup {...props} />);
return { wrapper };
};
const MOCK_LEFT = { type: 'reset', className: 'is-light', value: 'Reset' };
const MOCK_RIGHT = { type: 'button', className: 'is-primary', value: 'Search' };
However I am getting an error: TypeError: wrapper.find is not a function
The component I am testing looks like
import * as React from 'react';
const ButtonGroup = ({ leftBtn, rightBtn, leftHandler = null, rightHandler = null }) => (
<div className="field is-grouped is-grouped-right">
<p className="control">
<input type={leftBtn.type} className={'button ' + leftBtn.className} value={leftBtn.value} onClick={leftHandler} />
</p>
<p className="control">
<input type={rightBtn.type} className={'button ' + rightBtn.className} value={rightBtn.value} onClick={rightHandler} />
</p>
</div>
);
export default ButtonGroup;
I would like to essentially asset that on click, the action I expect is called.
Try
const setup = propOverrides => {
const props = Object.assign({ leftBtn: MOCK_LEFT, rightBtn: MOCK_RIGHT }, propOverrides);
const wrapper = shallow(<ButtonGroup {...props} />);
return wrapper;
};
Doing return { wrapper } is the same as return { wrapper: wrapper } so it's not your DOM element anymore, but an object with a property wrapper containing your DOM element.
This is a small bin to illustrate the issue.

Resources