How to test child component method without Enzyme? - javascript

I need to access and test a method of a child component in react using Jest. I am not using Enzyme, so this is not a duplicate of this question or this question. I would like to use React Testing Library instead of Enzyme.
Earlier I was happily accessing the methods I needed to test like this:
import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import App from "./App";
let container: any = null;
beforeEach(() => {
// setup a DOM element as a render target
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
// cleanup on exiting
unmountComponentAtNode(container);
container.remove();
container = null;
});
test("methodToTest should do what I want it to", () => {
const { methodToTest } = render(<App />, container);
methodToTest("some input");
const output = document.querySelector(".outputFromMethod");
expect(output.innerHTML).toBe("You gave me some input");
});
But now I need to wrap my <App /> component in withRouter() from react-router-dom, so I have to do something like this: (Based on the recipe suggested by React Testing Library)
import React from "react";
import { BrowserRouter as Router } from "react-router-dom";
import { render, unmountComponentAtNode } from "react-dom";
import App from "./App";
let container: any = null;
beforeEach(() => {
// setup a DOM element as a render target
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
// cleanup on exiting
unmountComponentAtNode(container);
container.remove();
container = null;
});
test("methodToTest should do what I want it to", () => {
// THIS DOESN'T WORK NOW FOR GETTING methodToTest
const { methodToTest } = render(<Router><App /></Router>, container);
const output = document.querySelector(".outputFromMethod");
expect(output.innerHTML).toBe("You gave me some input");
});
I understand that it's not ideal to try to test methods on a child component. But I need to do this because I have to have this component render inside of a <Router>. Is there any way to access the <App /> components methods without using Enzyme, or using React Testing Library if necessary?

You can't do that with Testing Library, that's against the principles. You're also using a strange style for testing. Have you tried to do this:
import React from "react";
import { BrowserRouter as Router } from "react-router-dom";
import { render } from "#testing-library/react";
import App from "./App";
import "#testing-library/jest-dom/extend-expect";
test("methodToTest should do what I want it to", () => {
const { getByText } = render(<Router><App /></Router>);
expect(getByText("You gave me some input")).toBeInTheDocument();
});

Related

React Loadable and Meteor separate bundle

I am using the following Component with Meteor
https://github.com/CaptainN/npdev-react-loadable
import { Loadable } from 'meteor/npdev:react-loadable';
I create my Loadable component as follows
const HomePageBlog = Loadable({
loading: () => <FullPageLoader />,
loader: () => import('./HomePageBlog'),
});
I have gone through the SSR setup in the docs and it looks something like this
Server index.js
import React from 'react';
import { renderToString, renderToNodeStream } from 'react-dom/server';
import { onPageLoad } from 'meteor/server-render';
import { StaticRouter } from 'react-router';
import { Helmet } from 'react-helmet';
import Loadable from 'react-loadable';
import { ServerStyleSheet } from 'styled-components';
import {
LoadableCaptureProvider,
preloadAllLoadables,
} from 'meteor/npdev:react-loadable';
preloadAllLoadables().then(() => {
onPageLoad(async (sink) => {
const context = {};
const sheet = new ServerStyleSheet();
const loadableHandle = {};
const routes = (await import('../both/routes.js')).default;
const App = (props) => (
<StaticRouter location={props.location} context={context}>
{routes}
</StaticRouter>
);
const modules = [];
// const html = renderToNodeStream((
const html = renderToString(
<LoadableCaptureProvider handle={loadableHandle}>
<App location={sink.request.url} />
</LoadableCaptureProvider>,
);
// we have a list of modules here, hopefully Meteor will allow to add them to bundle
// console.log(modules);
sink.renderIntoElementById('app', html);
sink.appendToBody(loadableHandle.toScriptTag());
const helmet = Helmet.renderStatic();
// console.log(helmet);
sink.appendToHead(helmet.meta.toString());
sink.appendToHead(helmet.title.toString());
sink.appendToHead(helmet.link.toString());
sink.appendToHead(sheet.getStyleTags());
});
});
client index.js
import { Meteor } from 'meteor/meteor';
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, withRouter } from 'react-router-dom';
import { onPageLoad } from 'meteor/server-render';
import { createBrowserHistory } from 'history';
import { preloadLoadables } from 'meteor/npdev:react-loadable';
console.log('hi');
const history = createBrowserHistory();
/**
* If browser back button was used, flush cache
* This ensures that user will always see an accurate, up-to-date view based on their state
* https://stackoverflow.com/questions/8788802/prevent-safari-loading-from-cache-when-back-button-is-clicked
*/
(function () {
window.onpageshow = function (event) {
if (event.persisted) {
window.location.reload();
}
};
})();
onPageLoad(async () => {
const routes = (await import('../both/routes.js')).default;
const App = () => (
<>
<Router history={history}>
<div>{routes}</div>
</Router>
</>
);
preloadLoadables().then(() => {
ReactDOM.hydrate(<App />, document.getElementById('app'));
});
});
What I am trying to determine is what exactly react loadable does. I am wanting to separate my bundle so I can only load code via SSR when it is needed. Right now I have quite a low score on lighthouse for page speed.
The code that I have here works.
But what I expected to happen was have a separate request to grab more js for the loadable component when it is requested. So it's not in the initial bundle. Is this not how this package works.
Could someone one help me me understand this better.
Thanks for any help ahead of time

createRoot(...): Target container is not a DOM element in React Test file

I'm using React-18.0.0 in my project and in the test file I'm getting an error something below
createRoot(...): Target container is not a DOM element.
My test file is :
import ReactDOM from 'react-dom/client';
import { render, screen } from "#testing-library/react";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
test("renders learn react link", () => {
root.render(<App />);
const linkElement = screen.getByText(/Hello React/i);
expect(linkElement).toBeInTheDocument();
});
Your test uses root.render(<App />) while React's testing-library provides there own render function to use inside a test
Retrieving the root isn't needed, and is causing the error you're showing.
So, apply the following change:
// Remove
root.render(<App />);
// Replace with
render(<App />); // Imported from #testing-library/react
Example of working App.test.js:
import { render, screen } from '#testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/this should exist/i);
expect(linkElement).toBeInTheDocument();
});

Simple React button test fails

Im using jest to test a simple button in React and it keeps failing. My latest iteration complains about render. I'm new to testing and I've been at it for a while and cannot figure this out. What am I missing here?
Here's my App.js
function clickMe() {
alert("Hello")
}
function App() {
return (
<div className="App">
<button id="btn" onClick={clickMe}>Click Me!!</button>
</div>
)
}
export default App;
Here's my App.test.js
import React from 'react'
import {render} from 'react-dom'
import App from './App'
test("Click", () => {
const {container} = render(<App />)
const button = getByTestId(container, 'btn')
fireEvent.click(button)
})
You can simulate some events with the enzyme library
For this first install this by npm, then import that
import {shallow} from 'enzyme';
After using this structure to simulate a click on a bottom
Create a wrapper
let wrapper = shallow(<App />);
beforeEach( ()=>{
wrapper = shallow(<CounterApp />);
});
This creates a simulation of render components and gives you the capacity to simulate events, this method is working in my project counterApp
test('Click', ()=>{
wrapper.find('button').at(0).simulate('click');
expect(//The action of your botton).toBe(//the result expected);
});
Not exactly the same but something like this also worked
import { shallow } from 'enzyme'
import { configure } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import App from './App'
configure({ adapter: new Adapter() })
it('renders the link inside the output area', () => {
const output = shallow(<App />)
expect(output.find('div').find('button').length).toEqual(1)
})

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.

Categories