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.
Related
I have one component that that use one customized hook and I need to write some test and make sure I mock the hook. How can I mock the customized hook to have unit test for my component like the code of hook does not even exist?
import "./styles.css";
import useAPICall from "#src/hooks/useAPICall ";
export default function App() {
const { onAPICall } = useAPICall(123);
const handleOnClick = useCallback(() => {
onAPICall();
});
return (
<div className="App">
<button onClick={handleOnClick}>Click</button>
</div>
);
}
///test.tsx all I know is this. but I am not sure how I can use mockImplementation that does not return anything
jest.mock('#src/hooks/useAPICall', () => ({
onAPICall: () => jest.fn(),
}));
You can do it in three simple steps:
Import the module which you want to mock
Then mock the module
Provide the return value of the mocked module.
import React from 'react';
import { render } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
import App from './App';
import useAPICall from "#src/hooks/useAPICall "; // 1st step
jest.mock('#src/hooks/useAPICall'); //2nd step
test('can call useAPICall hook', () => {
useAPICall.mockReturnValue({ onAPICall: jest.fn() }); // 3rd step
const { getByRole } = render(<NewApp />);
userEvent.click(getByRole('button'));
expect(useAPICall).toBeCalled();
});
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.
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);
});
In jest, what is the difference between using shallow & render from enzyme?
Here is an example of both:
Test rendering with shallow:
import "jest";
import * as React from "react";
import { shallow } from "enzyme";
import Item from "../item.component";
describe("Item", () => {
it("renders correct", () => {
const item = {
name: "A"
};
const component = shallow(
<Item item={item}/>
);
expect(component).toMatchSnapshot();
});
});
Test rendering with render
import "jest";
import * as React from "react";
import { render } from "enzyme";
import Item from "../item.component";
describe("Item", () => {
it("renders correct", () => {
const item = {
name: "A"
};
const component = render(
<Item item={item}/>
);
expect(component).toMatchSnapshot();
});
});
What are the typical usage of these 2. I wrote all my tests with shallow, should I go back and change that to render in some cases?
shallow and render are specific to Enzyme, which can be used independently from Jest.
shallow renders only top-level component and doesn't require DOM. It is intended for isolated unit tests.
render renders the entire component, wraps it with Cheerio, which is jQuery implementation for Node.js. It is intended for blackbox integration/e2e tests with the aid of jQuery-like selectors. It may have is uses but shallow and mount are commonly used.
Neither of them are supposed to be used with toMatchSnapshot, at least without additional tools (enzyme-to-json for shallow and mount).
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