Testing a button's state with a withRouter-wrapped component - javascript

I have a component that is currently wrapped with withRouter (e.g. I use export default withRouter(myComponent)) since I need am using history.push for one of my links within the component. I am writing a test in Enzyme that tests whether a button in that component changes its state to true/false when the user clicks it. The test is failing with the error that it cannot read the property of isExpanded of null. This is what I have for my test:
import React from 'react';
import Adapter from 'enzyme-adapter-react-16';
import { mount, configure } from 'enzyme';
import { MemoryRouter } from 'react-router-dom';
import myComponent from './myComponent';
configure({ adapter: new Adapter() });
describe('Successful flows', () => {
test('button changes state when clicked', () => {
const wrapper = mount(<MemoryRouter><myComponent /></MemoryRouter>);
const moreBtn = wrapper.find('.seeMoreButton').at(0);
moreBtn.simulate('click');
expect(wrapper.state().isExpanded).toEqual(true);
});
});
I have found that before I used withRouter and just had const wrapper = mount(<myComponent />); in my test, the test passed. I am fairly new to routing and I feel like there's something I'm missing here so any help would be appreciated.

You are checking the state of the wrong component, the result of mount will be MemoryRouter, not myComponent.
After you mount the component, you'll need to find myComponent and verify its state instead
test('button changes state when clicked', () => {
const wrapper = mount(<MemoryRouter><myComponent /></MemoryRouter>);
const comp = wrapper.find(myComponent);
const moreBtn = comp.find('.seeMoreButton').at(0);
moreBtn.simulate('click');
expect(comp.state().isExpanded).toEqual(true);
});

Related

Mocking out React.Suspense Whilst Leaving the Rest of React Intact

I'm trying to write a Jest unit test for a component that uses React.Suspense.
Simplified versions of my component modules under test:
MyComponent.js
import React from 'react';
export default () => <h1>Tadaa!!!</h1>;
MySuspendedComponent.js
import React, { Suspense } from 'react';
import MyComponent from './MyComponent';
export default () => (
<Suspense fallback={<div>Loading…</div>}>
<MyComponent />
</Suspense>
);
Naïvely, in my first attempt, I wrote a unit test that uses Enzyme to mount the suspended component:
MySuspendedComponent.test.js
import React from 'react';
import { mount } from 'enzyme';
import MySuspendedComponent from './MySuspendedComponent';
test('the suspended component renders correctly', () => {
const wrapper = mount(<MySuspendedComponent />);
expect(wrapper.html()).toMatchSnapshot();
});
This causes the test to crash with the error message:
Error: Enzyme Internal Error: unknown node with tag 13
Searching for the error message on the web, I found that this is most likely caused by Enzyme not being ready to render Suspense (yet).
If I use shallow instead of mount, the error message changes to:
Invariant Violation: ReactDOMServer does not yet support Suspense
My next attempt was to mock out Suspense with a dummy pass-through component, like this:
MySuspendedComponent.test.js
import React from 'react';
import { mount } from 'enzyme';
import MySuspendedComponent from './MySuspendedComponent';
jest.mock('react', () => {
const react = require.requireActual('react');
return () => ({
...react,
Suspense({ children }) {
return children;
}
});
});
test('the suspended component renders correctly', () => {
const wrapper = mount(<MySuspendedComponent />);
expect(wrapper.html()).toMatchSnapshot();
});
The idea is to have a mock implementation of the React module that contains all the actual code from the React library, with only Suspense being replaced by a mock function.
I've used this pattern with requireActual, as described in the Jest documentation, successfully in other unit tests when mocking other modules than React, but with React, it does not work.
The error I get now is:
TypeError: (($_$w(...) , react) || ($$w(...) , _load_react(...))).default.createElement is not a function
…which, I assume, is caused by the original implementation of React not being available after my mocking trick.
How can I mock out Suspense while leaving the rest of the React library intact?
Or is there another, better way to test suspended components?
The solution is not to use object spreading to export the original React module, but simply overwriting the Suspense property, like this:
MySuspendedComponent.test.js
import React from 'react';
import { mount } from 'enzyme';
import MySuspendedComponent from './MySuspendedComponent';
jest.mock('react', () => {
const React = jest.requireActual('react');
React.Suspense = ({ children }) => children;
return React;
});
test('the suspended component renders correctly', () => {
const wrapper = mount(<MySuspendedComponent />);
expect(wrapper.html()).toMatchSnapshot();
});
This creates the following snapshot, as expected:
MySuspendedComponent.test.js.snap
exports[`the suspended component renders correctly 1`] = `"<h1>Tadaa!!!</h1>"`;
I needed to test my lazy component using Enzyme. Following approach worked for me to test on component loading completion:
const myComponent = React.lazy(() =>
import('#material-ui/icons')
.then(module => ({
default: module.KeyboardArrowRight
})
)
);
Test Code ->
//mock actual component inside suspense
jest.mock("#material-ui/icons", () => {
return {
KeyboardArrowRight: () => "KeyboardArrowRight",
}
});
const lazyComponent = mount(<Suspense fallback={<div>Loading...</div>}>
{<myComponent>}
</Suspense>);
const componentToTestLoaded = await componentToTest.type._result; // to get actual component in suspense
expect(componentToTestLoaded.text())`.toEqual("KeyboardArrowRight");
This is hacky but working well for Enzyme library.

Testing - React stateless components, spying on component methods using sinon

Problem:
I am trying to test the click function of a React stateless component in ES6. However, when using sinon to spy on the handleClick function I get the response...
TypeError: Cannot read property 'goToFullCart' of undefined
I have tried a few other methods. Some of them seem to work when the component is a Class component but all methods seem to fail when using a stateless functional component as follows...
The code:
import React from 'react'
import PropTypes from 'prop-types'
import SVG from '../../../../static/svg/Svg';
import Svgs from '../../../../static/svg/SvgTemplates';
const TestComponent = ({order, router}) => {
const handleClick = (event) => {
event.stopPropagation()
router.push(`/order/${order.orderId}/cart`);
}
return (
<button className="preview-bar" onClick={handleClick}>
<p>Button Content</p>
</button>
)
}
TestComponent.propTypes = {
order: PropTypes.object.isRequired,
router: PropTypes.object.isRequired,
}
export default TestComponent
export { TestComponent }
import React from 'react';
import { shallow, mount } from 'enzyme';
import { expect } from 'chai';
import sinon from 'sinon';
import TestComponent from "./TestComponent"
let wrapper, props;
describe('<TestComponent /> component unit tests', () => {
describe('when the minimized cart is clicked', () => {
beforeEach(function() {
props = {
}
wrapper = shallow((<TestComponent {...props} />))
});
it('should have a goToFullCart method', () => {
const spy = sinon.spy(TestComponent.prototype, 'handleClick');
expect(spy).to.equal(true)
});
});
});
I am at a loss as to how to spy on the handleClick method to check when the button is clicked.
Thanks for the help and discussion. Feel free to ask for any clarification if anything is not clear.
Cheers
A stateless component is basically a function. Good practice not to create another function inside this because it will create a closure, if you wanted to create methods inside component create stateful component rather
Instead of testing handleClick, you can test route is changed or not
import React from 'react';
import {shallow} from 'enzyme';
test('should call handleClick method when button got clicked', () => {
window.location.assign = mock;
wrapper .find('button').simulate('click');
expect(window.location.assign).toHaveBeenCalled(1);
});
Hope this help you!

React Native and Enzyme assertion error: Object not matching nested property

I am new to react native, enzyme and jest. I am trying to get a simple test working, to test child nodes. (Perhaps this is an incorrect way of trying to do so).
My Component is:
import React, { Component } from 'react';
import { View, Text, Button, TextInput } from 'react-native';
class MyComponent extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View >
<Button title="My Component"/>
</View>
)
}
}
export default MyComponent;
and my test is
import React from 'react';
import { configure, shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import MyComponent from '../components/MyComponent.js';
configure({ adapter: new Adapter() }) //setting up enzyme
const styles = require('../styles.js');
describe('rendering', () => {
it('checking View and Button exists', () => {
let wrapper
wrapper = shallow(<MyComponent/>);
expect(wrapper.find('View').children().find('Button')).toHaveProperty('title','My Component')
});
})
});
I am getting an error that the object return is not matching the expected:
Expected the object:
< listing of full object...>
To have a nested property:
"title"
With a value of:
"My Component"
The object returned shows MyComponent as a child of the root View, as well as the prop, but it is failing. Should I be doing this differently? I want to be able to create a test structure that will eventually confirm a number of child components and props under the View Component.
(as a side note, I would prefer to use Mocha, but I am coming up against this error which I haven't been able to resolve.
This other question helped me to answer my problem
https://stackoverflow.com/a/46546619/4797507 (apologies if I am not giving credit correctly)
The solution was for me to use:
expect(wrapper.find('View').children().find('Button').get(0).props.title).toEqual('My Component')

Why is this Jest/Enzyme setState test failing for my React app?

Expected:
Test runs and state is updated in the Login component, when then enables the Notification component (error message) to be found
Results:
Test fails, expected 1, received 0
Originally before I added redux and the store, thus needing to use the store and provider logic in my test, this Jest/Enzyme tests were passing.
The Login.test (updated current version)
import React from 'react'
import { Provider } from "react-redux"
import ReactTestUtils from 'react-dom/test-utils'
import { createCommonStore } from "../../store";
import { mount, shallow } from 'enzyme'
import toJson from 'enzyme-to-json'
import { missingLogin } from '../../consts/errors'
// import Login from './Login'
import { LoginContainer } from './Login';
import Notification from '../common/Notification'
const store = createCommonStore();
const user = {
id: 1,
role: 'Admin',
username: 'leongaban'
};
const loginComponent = mount(
<Provider store={store}>
<LoginContainer/>
</Provider>
);
const fakeEvent = { preventDefault: () => '' };
describe('<Login /> component', () => {
it('should render', () => {
const tree = toJson(loginComponent);
expect(tree).toMatchSnapshot();
});
it('should render the Notification component if state.error is true', () => {
loginComponent.setState({ error: true });
expect(loginComponent.find(Notification).length).toBe(1);
});
});
Login.test (previous passing version, but without the Redux store logic)
import React from 'react'
import ReactTestUtils from 'react-dom/test-utils'
import { mount, shallow } from 'enzyme'
import toJson from 'enzyme-to-json'
import { missingLogin } from '../../consts/errors'
import Login from './Login'
import Notification from '../common/Notification'
const loginComponent = shallow(<Login />);
const fakeEvent = { preventDefault: () => '' };
describe('<Login /> component', () => {
it('should render', () => {
const tree = toJson(loginComponent);
expect(tree).toMatchSnapshot();
});
it('should render the Notification component if state.error is true', () => {
loginComponent.setState({ error: true });
expect(loginComponent.find(Notification).length).toBe(1);
});
});
Your problem is that by mixing the redux store logic into the tests, the loginComponent variable no longer represents an instance of Login, but an instance of Provider wrapping and instance of Login.
Thus when you do this
loginComponent.setState({ error: true })
You're actually calling setState on the Provider instance.
I would recommend testing the LoginComponent you've wrapped with connect to produce LoginContainer separately from the store state. The Redux GitHub repo has a great article on testing connected components, which gives a general outline on how to do this.
To summarize what you need to do
Export both LoginComponent and LoginContainer separately
Test LoginComponent individually from the container, essentially doing what your previous working tests before mixing in redux store state did.
Write separate tests for LoginContainer where you test the mapStateToProps, mapDispatchToProps and mergeProps functionality.
Hope this helps!

React-Native Jest test function is called in componentDidMount

I'm trying to test if a function is called in the componentDidMount hook of my component.
I use React-Native and Jest to test my component.
The component looks like this:
const tracker = new GoogleAnalyticsTracker('UA-79731-33');
class MyComponent extends Component {
componentDidMount() {
tracker.trackScreenView(this.props.title);
}
}
So I'm mocking the GoogleAnalyticsTracker, it looks okay. Although I'm not sure how I can test that it has been called in the componentDidMount hook.
This is my test, which doesn't work:
import 'react-native';
import React from 'react';
import renderer from 'react-test-renderer';
import { GoogleAnalyticsTracker } from 'react-native-google-analytics-bridge';
import MyComponent from '../';
jest.mock('react-native-google-analytics-bridge');
const tracker = new GoogleAnalyticsTracker('ABC-123');
describe('MyComponent', () => {
it('renders', () => {
const tree = renderer.create(
<MyComponent />,
).toJSON();
expect(tree).toMatchSnapshot();
expect(tracker.trackScreenView).toBeCalled();
});
});
The toBeCalled() returns false.
How can I test that my function has been called in the componentDidMount?
Thanks
The react test rendered only calls the render method of a component and returns the output, it does not really start the component and so all the life cycle methods are not called. You should switch to the enzyme renderer which supports the full start of components using enzyme.mount

Categories