Testing React Component with Jest/Enzyme - reactjs

I am trying to use Enzyme's shallow wrapper to get the instance of my component and calling my class function over it. It shows me this error:
TypeError: tree.instance(...).onCampaignSelected is not a function
class ToolbarPage extends Component {
constructor(){
super();
this.onCampaignSelected = this.onCampaignSelected.bind(this);
this.state = {
item: null
}
}
onCampaignSelected (item) {
this.setState({item})
}
render () {
return (
<MyComponent onItemSelected={this.onCampaignSelected} />
)
}
}
function mapStateToProps(state){
buttons: state.toolbar.buttons
}
export default connect(mapStateToProps)(ToolbarPage);
My test case looks like this
import { shallow, mount } from 'enzyme';
import ToolbarPage from './ToolbarPage';
import configureStore from 'configureStore';
const store = configureStore();
const props = {
store,
isLoggedIn: false,
messageCounter: 0
}
describe('<ToolbarPage />', () => {
it('allows to select campaign', () => {
const tree = shallow(<ToolbarPage {...props}/>);
tree.instance().onCampaignSelected();
})
})
I also figured out that it is a wrapped component, so I won't get this function on the wrapped component. How do I access this function?

shallow does not render the full set of components with all of their properties & methods. It is intended for basic "did this thing render what I expected?" testing.
mount will give you everything and should allow you to test whatever you need. It is very useful for testing event handling & manipulating the state of components to test the interactions between components.

Related

How can i test the state in react functional components?

I am using jest and enzyme to test my components. If i have the following class component
import React, { Component } from "react";
export class Test extends Component {
state = {
isLoading: false,
};
onClick = () => {
this.setState({ isLoading: true });
};
render() {
return (
<div>
<button onClick={this.onClick}>Click button</button>
</div>
);
}
}
export default Test;
then to write test for this i will use:
import {Test} from "../Test";
import { shallow } from "enzyme";
import React from "react";
it('default state value for isLoading should be false', () => {
const wrapped = shallow(<Test/>);
const initialIsLoading = wrapped.state('isLoading');
expect(initialIsLoading).toBe(false);
})
it('change isLoading true on button click', () => {
const wrapped = shallow(<Test/>);
const initialIsLoading = wrapped.state('isLoading');
console.log(initialIsLoading);
wrapped.find('button').simulate('click');
wrapped.update();
const newValue = wrapped.state('isLoading');
console.log(newValue);
expect(newValue).toEqual(true);
})
but if my component is functional then i get error - state cannot be used on functional components
when i run my tests.
How can i test my functional components state ?
shallow wrapper doesn't support hooks, which provide state to functional components. You can either use Enzyme's mount wrapper or switch to React Testing Library which is better than Enzyme for modern generation of React.
Please note that mount can render stateful functional components, but not able to access the state. These will fail:
mount(<Test/>).setState();
mount(<Test/>).state();

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, Jest, Enzyme how to pass test App.js with store redux

Created a simple app using create-react-app then updated my App.js and added redux/store.
class App extends Component {
render() {
return (
<header className="header">
<h1>todos</h1>
</header>
);
}
}
function mapStateToProps(state) {
return {
data: state.data
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(ActionCreators, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
then trying to test my App on App.test.js using Enzyme and Jest.
import React from 'react'
import Enzyme, { mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16';
import App from './App'
Enzyme.configure({ adapter: new Adapter() });
function setup() {
const props = {
addTodo: jest.fn()
}
const enzymeWrapper = mount(<App {...props} />)
return {
props,
enzymeWrapper
}
}
describe('components', () => {
describe('App', () => {
it('should render self and subcomponents', () => {
const { enzymeWrapper } = setup()
expect(enzymeWrapper.find('header')).toBe(true)
})
})
})
but throwing error: Invariant Violation: Could not find "store" in either the context or props of "Connect(App)". Either wrap the root component in a , or explicitly pass "store" as a prop to "Connect(App)".
Any Ideas?
This is happening because you're trying to test the component without a <Provider> (which would normally make the store available to its sub-components).
What I normally do in this situation is test my component without the Redux connect binding. To do this, you can export the App component itself:
export class App extends Component // etc...
and then import that in the test file using the deconstructed assignment syntax:
import { App } from './App'
You can assume (hopefully... ;) ) that Redux and the React bindings have been properly tested by their creators, and spend your time on testing your own code instead.
There's a little more information about this in the Redux docs.

Testing Typescript React Component context with enzyme

I have a React Component in Typescript something like this
import * as React from 'react';
import * as PropTypes from 'prop-types';
export class MyComponent extends React.Component<{}, {}> {
context = {
callBack: Function
}
static contextTypes = {
callBack: React.PropTypes.Func
};
render() {
return (
<button onClick={this.callContextCallback}>Call Context</button>
);
}
callContextCallback = () => {
this.context.callBack();
}
}
And I have written my tests for the Component
import { mount, shallow } from 'enzyme'
import * as React from "react"
import { MyComponent } from "./MyComponent"
describe(`<MyComponent />`, () => {
let callBackMock = jest.fn()
beforeEach(() => {
wrapper = mount(
<MyComponent />, {
context: {
callBack: callBackMock
}
}
)
})
it(`should call context Callback on clicking button`, () => {
wrapper.find('button').simulate('click')
expect(callBackMock).toHaveBeenCalledTimes(1)
})
})
when I run my tests, The test fails with function not being called.
I even tried mocking spying on the callContextCalback function
it(`should call context Callback on clicking button`, () => {
let instance = wrapper.instance()
const spy = jest.spyOn(instance, 'callContextCallback')
instance.forceUpdate();
wrapper.find('button').simulate('click')
expect(spy).toHaveBeenCalledTimes(1)
})
and on running the test I get this error
Error: Uncaught [TypeError: Cannot read property 'context' of undefined]
TypeError: Cannot read property 'context' of undefined
How do I test the context callBack function?
The issue is resolved with help from the enzyme team.
Please refer to this Github Issue to know more about the solution.

Resources