React-Native Jest test function is called in componentDidMount - javascript

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

Related

Using IntlProvider with react-testing-library

For abstraction purpose, the app I work on has a custom provider that wraps a couple of other providers (like IntlProvider and CookiesProvider). The version of react-intl we use in our app is still v2 (react-intl#2.8.0). A simplified version of my App.js is:
App.js
return (
<Provider
value={{
localeData,
env,
data
}}>
<IntlProvider locale={language} key={language} messages={allMessages}>
{props.children}
</IntlProvider>
</Provider>
)
I have setup a custom render to test components in my app. My custom render looks exactly as it is specified in the react-intl-docs. I have followed the official setup guides from react-testing-library.
import React from "react";
import {render} from "#testing-library/react";
import {MyProvider} from "MyProvider";
const MyTestProvider = ({children}) => {
return <MyProvider>{children}</MyProvider>;
};
const myTestRender = (ui, options) => render(ui, {wrapper: MyTestProvider, ...options});
export * from "#testing-library/react";
export {myTestRender as render};
I then render my component under test as follows:
import {render as renderSC} from "test-utils";
import MyComponentUnderTest from 'MyComponentUnderTest';
test("does my component render", () => {
const {getByText} = renderSC(<MyComponentUnderTest />);
});
I get an error from react-intl which indicates it is warning, but the component that is rendered in test is empty.
console.error
Warning: IntlProvider.shouldComponentUpdate(): Returned undefined instead of a boolean value. Make sure to return true or false.
This is what my component renders in test:
<body>
<div />
</body>
Is anyone able to advise what I might be doing wrong?
I think your exports make this problem, try these instead:
import React from "react";
import {render} from "#testing-library/react";
import {MyProvider} from "MyProvider";
const MyTestProvider = ({children}) => {
return <MyProvider>{children}</MyProvider>;
};
const myTestRender = (ui, options) => render(ui, {wrapper: MyTestProvider, ...options});
export default myTestRender;
and then use your custom renderer this way:
import renderSC from "test-utils";
import MyComponentUnderTest from 'MyComponentUnderTest';
test("does my component render", () => {
const {getByText} = renderSC(<MyComponentUnderTest />);
});
So I was able to confirm that nothing was wrong with testing-library or with react-intl. The problem was the app I work on had a module mock that mocked out the functionality of react-intl. This was done for convenience when working with unit tests with Enzyme. However this module mock was stripping out all functionality I needed for IntlProvider and that was the reason I was seeing an empty div when I render the testing-library test with a wrapper.

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

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);
});

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')

Categories