How to test a component for a button click that has a function passed to it as props from the parent component - unit-testing

I have a parent component that keeps all the state and that renders my child component inside a Router. I'm passing a function from parent to child as props to handle the button click.
import React, {Component} from 'react';
import 'whatwg-fetch';
import arrayOfObjects from '../arrayOfObjects';
import {BrowserRouter as Router, Route} from 'react-router-dom';
import ChildComponent from './ChildComponent';
class Parent extends Component {
constructor() {
super();
this.fetchObject = this.fetchObject.bind(this);
const tempmyObject = {};
this.state = {
myObject: tempmyObject
};
}
fetchObject(event) {
const that = this;
fetch(path)
....
that.setState(
{
myObject: newObject
});
....
event.preventDefault();
};
render() {
return (
<Router>
<div>
<Route exact path='/GetUserInfo'
render={(props) => (
<ChildComponent {...props}
fields={arrayOfObjects}
myObject={this.state.myObject}
onChange={(event) => this.fetchObject(event)}
buttonName='foo'
/>
)}
/>
</div>
</Router>
);
}
}
export default Parent ;
import ShowChildComponent from './ShowChildComponent';
class ChildComponent extends Component {
render() {
return (
<div>
<ShowChildComponent
fields={this.props.arrayOfObjects}
myObject={this.state.myObject}
buttonName={this.props.name}
onChange={this.props.onChange}
/>
</div>
);
}
}
export default ChildComponent;
I'm using mount from enzyme and I would like to test it and simulate a button click in my unit tests, and then test for the changed data in the paragraph.
Previous to that, I had all state kept in the child component like this :
import React, {Component} from 'react';
import 'whatwg-fetch';
import ShowChildComponent from './ShowChildComponent';
class ChildComponent extends Component {
constructor(props) {
super(props);
this.fetchData = this.fetchData.bind(this);
const tempmyObject = {};
this.state = {
myObject: tempmyObject
};
}
fetchData(event) {
const that = this;
fetch(this.props.path)
.....
that.setState(
{
myObject: myObject,
});
....
event.preventDefault();
};
render() {
return (
<div>
<ShowChildComponent
fields={this.props.arrayOfObjects}
myObject={this.state.myObject}
buttonName={this.props.name}
onChange={this.fetchData}
/>
</div>
);
}
}
export default ChildComponent;
and I could simulate a click. My tests looked like this:
it('renders ChildComponent button click message', () => {
const wrapper = mount(<ChildComponent fields={arrayOfObjects}
myObject={myObject}
path={'/some/path'}
buttonName='foo'
/>);
const p = <p className='myParagraph' id={id}>{value}</p>;
wrapper.find('button.getData').simulate('click');
expect(wrapper.find(ShowChildComponent).containsMatchingElement(p)).toEqual(true);
});
How do I achieve the same thing after I refactored?

Related

reactstrap modal still existing in DOM during testing(#testing-library/react)

I'm testing a simple Modal component which is a wrapper of reactstrap component. I'm using #testing-library/react.
It works ok in browser but during the testing I sawed that the modal permanent exists in DOM even after unmount.
expect(props.toggleModal).toHaveBeenCalled();
Above assertion works good. So we have assurance that this function has been called and the modal state has been changed from true to false.
I checked in my Modal component and this is correct. So modal shouldn't be displayed (it works in browser).
/* eslint-disable react/prop-types */
import React, { useState } from "react";
import { render, fireEvent, cleanup } from "#testing-library/react";
import Modal from "./";
afterEach(cleanup);
const props = {
toggleModal: jest.fn(),
title: "Fake title",
body: (
<div>
<p>Fake body</p>
</div>
),
footer: (
<ul>
<li>Link 1</li>
<li>Link 2</li>
</ul>
)
};
function App() {
const [modal, setModal] = useState(true);
props.toggleModal.mockImplementation(() => setModal(prevModal => !prevModal));
return (
<div id="app">
<Modal {...props} isOpen={modal} />
</div>
);
}
test("renders Modal component", () => {
const { getByText, getAllByText } = render(<App />);
expect(getByText("Fake body")).toBeTruthy();
expect(getAllByText("Link", { exact: false })).toHaveLength(2);
fireEvent.click(document.querySelector("button"));
expect(props.toggleModal).toHaveBeenCalled();
console.log(document.body.innerHTML);
});
//Modal.js
import React from "react";
import PropTypes from "prop-types";
import {
Modal as BootstrapModal,
ModalHeader,
ModalBody,
ModalFooter
} from "reactstrap";
class Modal extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<BootstrapModal
isOpen={this.props.isOpen}
toggle={this.props.toggleModal}
>
{this.props.title && (
<ModalHeader toggle={this.props.toggleModal}>
{this.props.title}
</ModalHeader>
)}
{this.props.body && <ModalBody>{this.props.body}</ModalBody>}
{this.props.footer && <ModalFooter>{this.props.footer}</ModalFooter>}
</BootstrapModal>
);
}
static propTypes = {
isOpen: PropTypes.bool,
title: PropTypes.string,
body: PropTypes.element,
footer: PropTypes.element,
toggleModal: PropTypes.func
};
}
export default Modal;
I think that modal shouldn't exist in DOM so in this example document.body.innerHTML should have only <div><div id="app"></div></div>

Why do I get method is not a function in my jesttest?

My jest unittest looks like this:
import React from 'react';
import renderer from 'react-test-renderer';
import ReactTestUtils from 'react-dom/test-utils'
import Calculator from "./calculator";
test('test that calculator', () => {
const component = renderer.create(
<Calculator></Calculator>
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
console.log('component=',component.refs);
// Simulate click on button -> trigger sumCalc()
ReactTestUtils.Simulate.click(component.refs.button);
});
When I run the test I get:
TypeError: Cannot read property 'button' of undefined
My react component looks like this:
import React, {Component} from 'react';
export default class Calculator extends Component {
constructor(props) {
super(props);
this.calcSum = this.calcSum.bind(this);
this.state = {sum: 0};
}
calcSum() {
console.log('this.refs.one=', this.refs.one);
let s = Number(this.refs.one.value) + Number(this.refs.two.value);
this.setState({sum: s});
}
render() {
return (<div>
<input type="text" placeholder="number 1" ref="one"/>
<input type="text" placeholder="number 2" ref="two"/>
<button ref="button" onClick={this.calcSum}>sum</button>
sum: {this.state.sum}
</div>
);
}
}
How can I avoid this error? what am I missing?
The component works when rendered into the DOM but the unit test has issues.
component.toJSON() returns a JSON not a JavaScript object. Moreover calcSum is not a prop, instead it is a method defined on your component class.
Hence you could use getInstance() method to manually invoke calcSum.
Try this:
const component = renderer.create(<Calculator />);
component.getInstance().calcSum();
Now you can see that console.log output from calcSum.

How to unit test a method of react component?

I am trying to unit test my reactjs component:
import React from 'react';
import Modal from 'react-modal';
import store from '../../../store'
import lodash from 'lodash'
export class AddToOrder extends React.Component {
constructor(props) {
super(props);
this.state = {checked: false}
//debugger
}
checkBoxChecked() {
return true
}
render() {
console.log('testing=this.props.id',this.props.id )
return (
<div className="order">
<label>
<input
id={this.props.parent}
checked={this.checkBoxChecked()}
onChange={this.addToOrder.bind(this, this.props)}
type="checkbox"/>
Add to order
</label>
</div>
)
}
}
export default AddToOrder;
Just to get started I am already struggling to assert the checkBoxChecked method:
import React from 'react-native';
import {shallow} from 'enzyme';
import {AddToOrder} from '../app/components/buttons/addtoorder/addtoorder';
import {expect} from 'chai';
import {mount} from 'enzyme';
import jsdom from 'jsdom';
const doc = jsdom.jsdom('<!doctype html><html><body></body></html>')
global.document = doc
global.window = doc.defaultView
let props;
beforeEach(() => {
props = {
cart: {
items: [{
id: 100,
price: 2000,
name:'Docs'
}]
}
};
});
describe('AddToOrder component', () => {
it('should be handling checkboxChecked', () => {
const wrapper = shallow(<AddToOrder {...props.cart} />);
expect(wrapper.checkBoxChecked()).equals(true); //error appears here
});
});
```
How can I unit test a method on the component? This is the error I am getting:
TypeError: Cannot read property 'checked' of undefined
You are almost there. Just change your expect to this:
expect(wrapper.instance().checkBoxChecked()).equals(true);
You can go through this link to know more about testing component methods using enzyme
For those who find the accepted answer as not working, try using .dive() on your shallow wrapper before using .instance():
expect(wrapper.dive().instance().somePrivateMethod()).toEqual(true);
Reference: Testing component methods with enzyme
Extend of previous answer.
If you have connected component (Redux) , try next code :
const store=configureStore();
const context = { store };
const wrapper = shallow(
<MyComponent,
{ context },
);
const inst = wrapper.dive().instance();
inst.myCustomMethod('hello');

How to stop redux-form or React from changing htmlFor and id when creating Jest snapshots?

I've got a wizard form made with redux-forms v6 and it looks something like:
--
index.js - Holds page number in local state, is connected to application level state
PageOne - wrapped with reduxForm decorator (form: 'wizForm')
PageTwo - wrapped with reduxForm decorator (form: 'wizForm')
--
PageOne and PageTwo both contain additional components that render sections of the form (initial fields, vehicle information, driver information...), and each of those sections render their own components for each question in that section.
Since there's a lot of nested components and I want to test that PageOne and PageTwo call the props passed from index.js, I've resorted to using Enzyme's mount() function with a fake store. I want to MatchSnapshot() with Jest to compare whether index.js is rendering PageOne or PageTwo, after certain buttons are clicked to go back and forth from pages.
The problem is when I do create snapshots, other than creating a 16,000 line snapshot, the snapshot will NEVER match the previous one even if I don't change anything. I'm not sure if it's redux-form that's doing it or React, but the htmlFor and the id keep changing between snapshots, test after test after test.
We use css-modules too, but I don't think that's causing the problem, and we did configure Jest to work with css-modules too, modifying "moduleNameWrapper" to mock .css files. Does anyone know how to fix this or where I should look?
tests:
describe('<VehicleAddition />', () => {
let props;
beforeEach(() => {
props = {
...,
};
});
it('Renders initially', () => {
const component = shallow(<VehicleAddition {...props} />);
expect(toJson(component)).toMatchSnapshot();
});
it('Renders <PageTwo> when <PageOne> form is submitted', () => {
const component = shallow(<VehicleAddition {...props} />);
expect(toJson(component)).toMatchSnapshot();
component.find('ReduxForm') // reduxForm HOC wraps the <form> in a <ReduxForm> component
.first()
.simulate('submit');
expect(toJson(component)).toMatchSnapshot();
expect(component.state().page).toEqual(2);
});
it('PageTwoStuffs', () => {
// Render the form, click 'next', assert it's page two
// click 'previous'
jest.enableAutomock();
const store = createStore(
combineReducers({
route: jest.fn(() => Immutable.fromJS({})),
language: jest.fn(() => Immutable.fromJS({})),
global: jest.fn(() => Immutable.fromJS({})),
form: formReducer,
}),
Immutable.fromJS({}),
);
const component = mount(
<Provider store={store}>
<VehicleAddition {...props} />
</Provider>
);
// CAN'T check the state of <VehicleAddition /> because it can only be done on root component, says the error message.
expect(toJson(component)).toMatchSnapshot();
index.js:
export class VehicleAddition extends React.Component { // eslint-disable-line
constructor(props) {
super(props);
this.state = {
page: 1,
};
}
nextPage = () => {
this.setState({ page: this.state.page + 1 });
}
previousPage = () => {
this.setState({ page: this.state.page - 1 });
}
render() {
return (
<div>
{page === 1 &&
<PageOne
{...this.props}
/>
}
{page === 2 &&
<PageTwo
{...this.props}
/>
}
</div>
);
}
}
PageOne.js
class PageOne extends React.Component { // eslint-disable-line
render() {
const {
...
} = this.props;
return (
<form onSubmit={handleSubmit}>
<div>
<InitialFields
autoPolicies={autoPolicies}
changeField={this.changeField}
getFormValues={getFormValues}
policies={policies}
primary={primary}
/>
<VehicleBeingAddedFields
changeField={this.changeField}
getFormValues={getFormValues}
fetchVehMakes={fetchVehMakes}
fetchVehModels={fetchVehModels}
policies={policies}
vehMakes={vehMakes}
vehModels={vehModels}
/>
...
<div className="btn-group btn-group-float-right">
<button
type="submit"
onClick={this.handleClick}
disabled={pristine || submitting}
className="btn-primary"
>
Next
</button>
</div>
</form>
);
}
}
PageTwo.js:
class PageTwo extends React.Component { // eslint-disable-line
render() {
const {
...
} = this.props;
return (
<form onSubmit={handleSubmit}>
...
<div className="btn-group btn-group-float-right">
<button type="button" className="btn" onClick={previousPage}>Previous</button>{' '}
<button type="submit" disabled={pristine || submitting} className="btn-primary">Submit</button>
</div>
</form>
);
}
}
Example of the parts of the snapshot that constantly changes:
I solved it by passing a hardcoded id value from the test cases
import React from 'react';
import renderer from 'react-test-renderer';
import { reduxForm } from 'redux-form';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { mount } from 'enzyme'
import TodoItem from './TodoItem';
import injectTapEventPlugin from 'react-tap-event-plugin';
function setup() {
const spy = jest.fn();
const store = createStore(() => ({}));
const Decorated = reduxForm({ form: 'testForm' })(TodoItem);
const props = {
remove: jest.fn(),
TodoItemReduxFormInitialName: "fullName",
snapshotTestId:"4"
}
const mockedComponent = <Provider store={store}>
<Decorated {...props} />
</Provider>;
const enzymeWrapper = mount(mockedComponent)
injectTapEventPlugin();
return {
props,
mockedComponent,
enzymeWrapper
}
}
describe('TodoItem Component', () => {
it('should render the snapshot', () => {
const {mockedComponent} = setup()
const tree = renderer.create(
mockedComponent
).toJSON();
expect(tree).toMatchSnapshot();
});
//not required as snapshot testing covers it
it('should render Number', () => {
const {enzymeWrapper} = setup()
const fieldProps = enzymeWrapper.find('Field').at(0).props();
expect(fieldProps.hintText).toEqual('Item Number');
expect(fieldProps.name).toEqual('fullName.itemNumber');
});
//not required as snapshot testing covers it
it('should render remove button', () => {
const {enzymeWrapper} = setup()
const button = enzymeWrapper.find('RaisedButton').at(0).props();
expect(button.label).toEqual("remove")
});
});

How to mock Picker and Picker.Item with jest in React-Native?

I am trying to snapshot test this snippet of code:
import React, { Component } from 'react';
import {
Picker,
} from 'react-native';
export default class TestComponent extends Component {
render() {
return (
<Picker
selectedValue={this.props.asset.type}
onValueChange={this.props.onTypeChange}>
<Picker.Item label="Type of asset" value="default" />
<Picker.Item label="Car" value="car" />
<Picker.Item label="Boat" value="boat" />
<Picker.Item label="Ship" value="ship" />
</Picker>
);
}
}
My test looks like this right now:
import 'react-native';
import React from 'react';
import TestComponent from './TestComponent';
import renderer from 'react-test-renderer';
describe('TestComponent', () => {
const asset = {
type: 'car',
}
it('renders correctly', () => {
const tree = renderer.create(
<TestComponent
asset={asset} />
).toJSON();
expect(tree).toMatchSnapshot();
});
})
My problem is that I get:
TypeError: Cannot read property '_tag' of undefined
I think that I should mock it based on this issue
I have tried adding simply:
jest.mock('Picker', () => 'Picker')
But than it still throws an error because Picker.Item is still not mocked
Invariant Violation: Element type is invalid: expected a string (for built-
in components) or a class/function (for composite components)
but got: undefined. Check the render method of `TestComponent`.
Other variants I tried with no avail:
jest.mock('Picker', () => {return {Item: 'Item'}});
----------------------------------------------------
class Picker{
Item = 'PickerItem'
}
jest.mock('Picker', () => {
return Picker;
});
Created a github issue as well and here is a working answer:
jest.mock('Picker', () => {
const Picker = class extends Component {
static Item = props => React.createElement('Item', props, props.children);
static propTypes = { children: React.PropTypes.any };
render() {
return React.createElement('Picker', this.props, this.props.children);
}
}
return Picker;
})
For Expo v39, I was able to test #react-native-community/picker by adding the following mock to my test/setup file:
jest.mock('#react-native-community/picker', () => {
const React = require('React')
const RealComponent = jest.requireActual('#react-native-community/picker')
class Picker extends React.Component {
static Item = (props: { children: never }) => {
return React.createElement('Item', props, props.children)
}
render () {
return React.createElement('Picker', this.props, this.props.children)
}
}
Picker.propTypes = RealComponent.propTypes
return {
Picker
}
})
Note that #react-native-community/picker is now react-native-picker/picker.
https://jestjs.io/docs/en/tutorial-react-native#tips