How to mock a wrapper component with Jest? - reactjs

Assume the component is working, I have just removed useless code. In my component to test, I have this:
import {Container} from "flux/utils";
import Dialog from "material-ui/Dialog";
import React from "react";
...
class Component extends React.Component {
state;
static calculateState() {
return {
...
};
}
static getStores() {
return [...];
}
...
render() {
var actions = [...];
return <Dialog
actions={actions}
open={this.state.open}
title="foo"
...
>
<form ...>
{/* My inputs and their values are according to the state. */}
</form>
</Dialog>;
}
}
const MyComponent = Container.create(Component);
export default MyComponent;
In the UI, the inputs and their values are working. However, when I use Jest, it is not.
jest.mock('react-dom');
jest.mock('material-ui/Dialog', () => 'Dialog');
import Dispatcher from "../../dispatcher/Dispatcher";
import MyComponent from "../MyComponent";
import React from "react";
import Renderer from "react-test-renderer";
describe('MyComponent', () => {
it('creates', () => {
const component = Renderer.create(<MyComponent/>);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
Dispatcher.dispatch({
action: 'myComponent/open'
});
tree = component.toJSON();
expect(tree).toMatchSnapshot();
tree.children[0].children[1].children[0].props.onChange(undefined, undefined, 'john');
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
The first snapshot is good. The second snapshot is good too, the open property is changing. However, when I trigger the onChange of an input, the component state is updating, all good... but when I do the last snapshot, the children are still the same, event if the input values are different (and the state is well updated, confirmed).
I know this is due to the Dialog. If I remove it from the component class, the component works as expected... So how should I declare the mock?
Here is my last try:
jest.mock('material-ui/Dialog', () => {
var React = require("react");
return class extends React.Component {
shouldComponentUpdate() {
return true;
}
render() {
var {children, ...rest} = this.props;
return children;
}
}
});
The children are still the same :(

Related

Access to component methods in React

I'm using a component called react-input-autosize. The issue I'm facing is that it won't resize the input on viewport resize, so I wan't to manually hook into the component methods and run copyInputStyles() and updateInputWidth().
Pretty new to React so don't know how to achieve this. You can expose the input via the inputRef, but that doesn't really help me no?
My current reduced test case looks like this, would be happy with any pointers on how to run the component methods.
import React from 'react';
import styled from '#emotion/styled';
import AutosizeInput from 'react-input-autosize';
import {throttle} from 'lodash';
const StyledAutosizeInput = styled(AutosizeInput)`
max-width: 100vw;
`;
export default class signUp extends React.Component {
constructor (props) {
super(props);
this.inputRef = React.createRef();
}
componentDidMount() {
console.log(this.inputRef); // outputs input node
window.addEventListener('resize', this.resize);
this.resize();
}
componentWillUnmount() {
window.removeEventListener('resize', this.resize);
}
resize = throttle(() => {
console.log('force resize');
}, 100);
render() {
return (
<StyledAutosizeInput
inputRef={node => this.inputRef = node}
/>
);
}
}
The inputRef={node => this.inputRef = node} callback is referring to the html input element and not the AutosizeInput component. Pass the ref via the ref prop to access the component's methods.
...
resize = throttle(() => {
if (this.inputRef.current) {
this.inputRef.current.copyInputStyles()
this.inputRef.current.updateInputWidth()
}
}, 100);
render() {
return (
<StyledAutosizeInput
ref={this.inputRef}
/>
);
}

How to test HOC with enzyme, chai

I have a HOC function that receives a React component and returns that react component with two new method properties (handleBack & moveitOnTop) like so:
import React, { Component } from "react";
import classNames from "classnames";
export default WrappedComponent => {
return class extends Component {
constructor(props) {
super(props);
this.moveitOnTop = this.moveitOnTop.bind(this);
this.handleBack = this.handleBack.bind(this);
this.state = {
isSearchActive: false
};
}
moveitOnTop(flag) {
this.setState({ isSearchActive: flag });
window.scrollTo(0, -100);
}
handleBack() {
this.setState({ isSearchActive: false });
if (document.body.classList.contains("lock-position")) {
document.body.classList.remove("lock-position");
}
}
render() {
const props = {
...this.props,
isSearchActive: this.state.isSearchActive,
moveitOnTop: this.moveitOnTop,
goBack: this.handleBack
};
const classes = classNames({ "c-ftsOnTop": this.state.isSearchActive });
return (
<div className={classes}>
<WrappedComponent {...props} />
</div>
);
}
};
};
The component:
//import fireAnalytics
import { fireAnalytics } from "#modules/js-utils/lib";
class MyComponent extender Component{
constructor(){
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
// calling analytics function by passing vals
fireAnalytics({
event: "GAEvent",
category: "",
action: `Clicked on the ${e.target.id} input`,
label: "Click"
});
// I CALL THE HOC PROPERTY
this.props.moveitOnTop(true);
// I CALL THE HOC PROPERTY
this.props.handleBack();
}
render(){
return(
<div className="emailBlock">
<input type="text" onClick={handleClick} />
<Button className="submit">Submit</Button>
</div>
)
}
}
// export HOC component
export default hoc(MyComponent);
// export just MyComponent
export {MyComponent};
I want to test the HOC:
I need to check that class .c-ftsOnTop exists
I need to check onClick function that calls this.props.handleBack & `this.props.moveitOnTop'
I need to check if className emailBlock exists.
The test that I tried, but fails:
import { mount, shallow } from 'enzyme';
import sinon from 'sinon';
import React from 'react';
import { expect } from 'chai';
import hoc from '....';
import {MyComponent} from '...';
import MyComponent from '....';
it('renders component', () => {
const props = {}
const HocComponent = hoc(MyComponent);
const wrapper = mount(
<HocComponent {...props} />
);
console.log('wrapper:', wrapper);
expect(wrapper.find('.c-ftsOnTop')).to.have.lengthOf(1);
expect(wrapper.hasClass('c-fts-input-container')).toEqual(true);
})
Error
AssertionError: expected {} to have a length of 1 but got 0
console.log: wrapper: ReactWrapper {}
Can anybody help me on how to render the HOC?
Here is a working test:
import { mount } from 'enzyme';
import React from 'react';
import WrappedMyComponent from './MyComponent';
it('renders component', () => {
const props = {}
const moveitOnTopSpy = jest.spyOn(WrappedMyComponent.prototype, 'moveitOnTop');
const handleBackSpy = jest.spyOn(WrappedMyComponent.prototype, 'handleBack');
const wrapper = mount(
<WrappedMyComponent {...props} />
);
// 1. I need to check that class .c-ftsOnTop exists
wrapper.setState({ isSearchActive: true }); // <= set isSearchActive to true so .c-ftsOnTop is added
expect(wrapper.find('.c-ftsOnTop')).toHaveLength(1); // Success!
// 2. I need to check onClick function that calls this.props.handleBack & `this.props.moveitOnTop'
window.scrollTo = jest.fn(); // mock window.scrollTo
wrapper.find('input').props().onClick();
expect(moveitOnTopSpy).toHaveBeenCalled(); // Success!
expect(window.scrollTo).toHaveBeenCalledWith(0, -100); // Success!
expect(handleBackSpy).toHaveBeenCalled(); // Success!
// 3. I need to check if className emailBlock exists
expect(wrapper.find('.emailBlock')).toHaveLength(1); // Success!
})
Details
.c-ftsOnTop is only added when isSearchActive is true so just set the state of the component so the class is added.
If you create your spies on the prototype methods for moveitOnTop and handleBack, then when the hoc creates its instance methods by binding them to this in the constructor, the instance methods will be bound to your spies.
window.scrollTo logs an error to the console by default in jsdom so you can mock it to avoid that error message and to verify that it was called with the expected arguments.
Note that the above test requires the following typos to be fixed in MyComponent:
extender should be extends
constructor should take a props argument
onClick should be bound to this.handleClick instead of just handleClick
handleClick should call this.props.goBack() instead of this.props.handleBack()
(I'm guessing MyComponent was just thrown together as an example of the actual component)

Testing component function in React with Enzyme and Jest

I am testing a component following the Container/Presentational pattern. What I need in order to have my coverage at 100% is to test the handleChange() function in which I have a setState(). The thing is that I'm just testing the Container component and the function is called in Presentational one.
Container Component:
import React, { Component } from 'react'
import DataStructureFormView from './DataStructureFormView'
export default class DataStructureForm extends Component {
state = {}
handleChange = name => event => {
this.setState({
[name]: event.target.value
})
}
render() {
return (
<DataStructureFormView
handleChange={this.handleChange}
form={this.state}
/>
)
}
}
As you can see, the DataStructureForm is the Container component and DataStructureFormView is the Presentational one.
Test file:
import React from 'react'
import { shallow, mount } from 'enzyme'
describe('DataStructureForm', () => {
it('should call the handleChange() function and change the state', () => {
const component = mount(<DataStructureForm />)
const handleChange = jest.spyOn(component.instance(), 'handleChange')
component.instance().handleChange()
expect(handleChange).toBeCalled()
}
}
This is one of the multiple approaches that I have done, but it is not testing the setState() inside the handleChange() method.
What else I can do?
The most straightforward solution is to check if state changed accordingly:
const component = shallow(<DataStructureForm />)
const value = 'someValue';
component.instance().handleChange('someName')({target: { value }});
expect(component.state('someName')).toEqual(value)

React - Wait for complex method to finish before rendering

I'm trying to display a dashboard component, crunching a lot of data fetched from my redux store. This component takes a lot of time to render, mainly because of a single complex method.
Is it possible to render some kind of loader or placeholder while this method is processing ?
I tried doing so by using ComponentDidMount, but it seems like, because the method is part of my render() method, it will always be triggered first-hand.
Yes! Check out this tutorial.
Loader:
import React, {Component} from 'react';
const asyncComponent = (importComponent) => {
return class extends Component {
state = {
component: null
}
componentDidMount() {
importComponent()
.then(cmp => {
this.setState({component: cmp.default});
});
}
render() {
const C = this.state.component;
return C ? <C {...this.props}/> : null;
}
}
};
export default asyncComponent;
Usage:
import React from 'react';
import asyncComponent from '../../hoc/asyncComponent';
const AsyncButton = asyncComponent(() => {
return import('../Button');
});
const container = () => {
return (
<div>
<h1>Here goes an async loaded button component</h1>
<AsyncButton/>
</div>
);
};
export default container;
or check out this library.

React: onChange is not a function

Am new to react and am trying to run unit test for a react app using jest,
The app using the Flux pattern and I have already wrote one component "WEATHER" and before move on to another one I wanted to write test for that component and take TDD approach.
My code running fine but the test fail with this error
TypeError: tree.props.onChange is not a function
The weather component code :
// #flow
import React from 'react';
import api from '../api';
import weatherStore from '../stores/weatherStore';
//read from weather store
let _getState = () => {
return {weather: weatherStore.returnWeather()};
};
export default class Weather extends React.Component {
constructor(proporties) {
super(proporties);
this._onChange = this._onChange.bind(this);
this.state = _getState();
}
componentWillUnmount() {
weatherStore.removeListener('change', this._onChange);
}
componentDidMount() {
api.getWeather();
weatherStore.on('change', this._onChange);
}
_onChange() {
this.setState(_getState());
}
render() {
let weatherState = this.state.weather.map(weather => {
return <div key={weather.id} className="pull-right row">
<div>
<span>{weather.main.temp} C</span><br />
</div>
</div>;
})
return <div>{weatherState}</div>;
}
}
Where the test code:
import React from 'react';
import Weather from '../js/components/Weather';
import renderer from 'react-test-renderer';
it('should render weather temp and city', ()=> {
const component = renderer.create(
<Weather />
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
// manually trigger the callback
tree.props._onChange();
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
Note since am using flow static type checker it always high light this line of code this._onChange = this._onChange.bind(this);
with error message saying : property '_onChange' property not found in the weather.
TypeError: tree.props.onChange is not a function
Have you tried updating your test with:
// manually trigger the callback
tree.props._onChange();
Note the dash.

Resources