Trouble-shooting a basic test with React and Jest - reactjs

I have setup a basic test with React and Jest. It seems to be failing for some reason.
import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-addons-test-utils';
// import App from '../src/client/app.jsx';
const App = (props) => (
<div>Hello world</div>
);
it('App renders hello world', () => {
const app1 = TestUtils.renderIntoDocument(<App />);
const appNode = ReactDOM.findDOMNode(app1);
console.log(app1, appNode);
expect(appNode.textContent).toEqual('Hello world');
});
Both app1 and appNode are null when printed to the console. Any help?
Screenshot below:

TestUtils and React Stateless Components:
the suggested solution is to wrap the component inside of another
component such as a DIV.
const app1 = TestUtils.renderIntoDocument(<div><App /></div>); // this should work

Related

Unit testing Chakra UI with Jest

Currently I am trying to unit test my application that is built with Create-React-App with typescript, and it is styled with chakraui. Chakrui includes a component ThemeProvider that must wrap the entire application as such.
This is my index.tsx file
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { ThemeProvider, CSSReset } from "#chakra-ui/core/dist";
import { theme } from "#chakra-ui/core/dist";
ReactDOM.render(
<React.StrictMode>
<ThemeProvider theme={theme}>
<CSSReset />
<App />
</ThemeProvider>
</React.StrictMode>,
document.getElementById("root")
For every unit test that I write, I am having to wrap the component with ThemeProvider for the test to pass:
import React from "react";
import { render } from "#testing-library/react";
import { ThemeProvider } from "#chakra-ui/core/dist";
import App from "./App";
describe("<App />", () => {
test("smoke test", () => {
render(
<ThemeProvider>
<App />
</ThemeProvider>
);
});
});
But this is very verbose, and must be done for every test that I write. Is there a way to do this just once in each .test.tsx file?
You could create your own theme wrapper function
import React from "react";
import { ThemeProvider } from "#chakra-ui/core/dist";
export const ThemeWrapper = ({ children }) => (
<ThemeProvider>{children}</ThemeProvider>
);
And then specify the wrapper in the test
import React from "react";
import { render } from "#testing-library/react";
import { ThemeWrapper } from "../testUtils";
import App from "./App";
describe("<App />", () => {
test("smoke test", () => {
render(<App />, { wrapper: ThemeWrapper });
});
});
This marginally reduces the code for testing. You may be able to also go the route of creating a custom render function (following the steps for redux).
It could look something like
import React from "react";
import { render } from "#testing-library/react";
import { ThemeProvider } from "#chakra-ui/core/dist";
export const renderWithTheme = ui => {
const Wrapper = ({ children }) => (
<ThemeProvider>{children}</ThemeProvider>
);
return render(ui, { wrapper: Wrapper });
};
Basically the same as the wrapper above, but more integrated into a test render function. You can adjust the function signature a bit as well if you need to pass in a theme object, or other render options, this is just a simple example.
Now the test looks like
import React from "react";
import { renderWithTheme } from "../testUtils";
import App from "./App";
describe("<App />", () => {
test("smoke test", () => {
renderWithTheme(<App />);
});
It might be the case that Jest might be mocking your imports from #chakra-ui/core/dist (depending on your jest configuration) which might be resulting your imported chakra-ui components to be undefined.
Importing the Theme Provider and wrapping it everytime with your renders might be one way to do it. The problem might arise when you have multiple components in your index.tsx. So, you might not want to import each and every component.
In that case, you will want to import the actual components from #chakra-ui/core.
The best way (according to me) to do so in Jest is:
jest.mock("#chakra-ui/core", () => {
const ui = jest.requireActual("#chakra-ui/core");
return {
...ui,
customKey: 'customValue',
};
})
This way you can even add custom function and key-values to the imported module.

How to set intellisense for react testing library using typescript

I just downloaded Create-React-App which uses react testing library. I do not get intellisense when I use it's methods (Example : toBeInTheDocument) . How to set intellisense ? Do we have any type definitions for RTL?
import React from 'react';
import { render } from '#testing-library/react';
import App from './App';
test('renders learn react link', () => {
const { getByText } = render(<App />);
const linkElement = getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
You need to import '#testing-library/jest-dom'

Jest cannot find module from file in the same folder

I'm trying to set up a very basic test with Jest which tests whether App.js renders correctly. I am getting the error
Cannot find module './App' from 'App.test.js'
However, Jest was able to find:
'./App.js'
'./App.test.js'
However, if I try to write import App from "./App.js"; instead of ... from "./App";, I get
Cannot find module './App.js' from 'App.test.js'
How can I make Jest find modules properly?
The project was set up using Create React App, and App.js and App.test.js are located within the same folder (src/components).
App.js
import React, { Component } from "react";
class App extends Component {
render() {
return <div />;
}
}
export default App;
App.test.js
import React from "react";
import { shallow } from "enzyme";
import App from "./App.js";
const app = shallow(<App />);
it("renders correctly", () => {
expect(app).toMatchSnapshot();
});
I think Jest needs to set up itself before rendering components (so don't call <App /> outside of test cases):
it("renders correctly", () => {
const app = shallow(<App />);
expect(app).toMatchSnapshot();
});
However, if the problem is on the import line, assuming you use an up-to-date version and don't pass any CLI options yourself, I would recommend to replace whole content of App.test.js with:
it('', () => console.log(process.env))
and search for the listed environment variables in https://jestjs.io/docs/en/configuration to see if any can affect Jest.

Using react jest to test component created in requireJS, throwing error: ReferenceError: define is not defined

We are now creating component with reactJS inside backbone/requireJS project, below is a simple component I created:
define(function(require) {
var React = require('react');
var Step1Comp = React.createClass({
render: function() {
return <div>Step1</div>
}
});
return Step1Comp;
});
And this is the test:
'use strict';
jest.unmock('../../public/js/Step1Comp');
import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-dom';
import Step1Comp from '../../public/js/Step1Comp';
describe('a test testing comp', ()=>{
it('render comp', ()=>{
window.define={};
var step1Comp = TestUtils.renderIntoDocument(<Step1Comp />);
expect(TestUtils.isCompositeComponent(step1Comp)).toBeTruthy();
});
});
when we are running jest, I got this error:
Test suite failed to run
ReferenceError: define is not defined
The component has to be within define, as the main project is written in requireJS, and we have to wrap it in define so that this comp can be loaded with other component.
I have tried to add window['define']={} in the test to mock the define function, but it is useless.
Can anyone help me to resolve this issue?
Thanks in advance.
Update now as below:
jest.mock('define', () => {
});
import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-dom';
import Step1Comp from '../../public/js/app/create-quote/components/comps/details/step1/Step1Comp';
describe('a test testing comp', ()=>{
it('render comp', ()=>{
var step1Comp = TestUtils.renderIntoDocument(<Step1Comp />);
expect(TestUtils.isCompositeComponent(step1Comp)).toBeTruthy();
});
});
But when I run jest, still same error:
> NGCSC#1.0.0 test-faked /Users/frankhe/myjuniper-new/myjuniper/ngcsc-ui
> jest
FAIL __tests__/test_comp/test.jest.js
● Test suite failed to run
ReferenceError: define is not defined
RequireJS is not supported by Jest. it will be easier and most appropriate to mock the dependency at the top of Step1Comp.test.js:
jest.mock('amdefine', () => {
// mock implementation
})
import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-dom';
import Step1Comp from '../../public/js/Step1Comp';
describe('a test testing comp', ()=>{
it('render comp', ()=>{
var step1Comp = TestUtils.renderIntoDocument(<Step1Comp />);
expect(TestUtils.isCompositeComponent(step1Comp)).toBeTruthy();
});
});
This way, when Step1Comp is loaded, its dependency is already mocked, so it won't try to load the RequireJS module.
I can see the update, but I noticed you mocking define.
define is required from a dependency right? If yes then you need to mock the dependency and not the define.
here is the sample of what I'm talking about:
const define = require('amdefine')
then you need to mock the dependency and not the define
jest.mock('amdefine', () => {})
I hope you understand.

Test a create-react-app index.js file

I would like to have 100% coverage on my project.
In order to do so, I need to test my index.js file which is very basic :
index.js
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(<App/>, document.getElementById('root'));
I can't find how to test this.
When creating a function such as :
index.js
const index = (div) => {
ReactDOM.render(<App />, div || document.getElementById('root'));
};
and then testing it :
index.test.js
it('renders without crashing', () => {
const div = document.createElement('div');
index(div);
});
I get an error when importing index:
Invariant Violation: _registerComponent(...): Target container is not a DOM element.
PS:
Note that I already have the following test, working perfectly :
App.test.jsx
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App/>, div);
});
If 100% coverage on your project is your goal and the code in your index.js file is trivial, then it might be a good option to exclude the file from the coverage report, as Andreas Köberle points out in his answer.
Create-react-app currently only supports these four keys in the Jest configuration (source):
collectCoverageFrom
coverageReporters
coverageThreshold
snapshotSerializers
This is why
coveragePathIgnorePatterns": ["src/index.js"]
won't work.
Add following code to the most outer scope of your package.json file:
"jest": {
"collectCoverageFrom": [
"src/**/*.{js,jsx}",
"!src/index.js"
]
}
In the image below you see the output of a test run with this code added to the package.json of the initial app created with create-react-app v1.4.3. Note that the index.js file doesn't show up in the report anymore and also doesn't affect the coverage percentage.
This is how I've tested index.js
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(<App />, document.getElementById("root"));
index.test.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
jest.mock("react-dom", () => ({ render: jest.fn() }));
describe("Application root", () => {
it("should render without crashing", () => {
const div = document.createElement("div");
div.id = "root";
document.body.appendChild(div);
require("./index.js");
expect(ReactDOM.render).toHaveBeenCalledWith(<App />, div);
});
});
The main question is what you want to test there. If you want to test that your code works correct, write a unit test that spies on ReactDOM.render and mocks document.getElementById('root'). Cause this is all your code does, calling ReactDOM.render with our App component and a specific div.
import ReactDOM from 'react-dom';
...
jest.mock('react-dom', ()=> ({render: jest.fn()}))
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App/>, div);
global.document.getElementById = (id) => id ==='root' && div
expect(ReactDOM.render).toHaveBeenCalledWith(...)
});
If you want test that the app really starts in your page, you should write integration test with Selenium or Nightwatch.js
To just get 100% coverage you can also ignore this file by adding it to the coveragePathIgnorePatterns in your jest settings
I found an article online that explains this way to write the test...
// index.test.js
import Index from './index.js';
it('renders without crashing', () => {
expect(JSON.stringify(
Object.assign({}, Index, { _reactInternalInstance: 'censored' })
)).toMatchSnapshot();
});
Now change the index.js file accordingly:
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
export default ReactDOM.render(
<App />,
document.getElementById('root') || document.createElement('div')
);
Extending on dcastil's answer, here's how to skip these trivial files for a TypeScript project:
Edit package.json
At the root level add the following snippet
{
...rest of existing props,
"jest": {
"collectCoverageFrom": [
"src/**/*.{ts,tsx}",
"!src/serviceWorker.ts",
"!src/index.tsx"
]
}
}
Save and re-run coverage
By now coverage should be higher.
Inspired by the answer above by Shiraz. This is essentially the same solution but for React 17/18. Also it provides some additional test coverage.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode />,
);
import React from 'react';
import ReactDOM from 'react-dom';
const render= jest.fn().mockName('render');
jest.mock('react');
jest.mock('react-dom', () => ({
createRoot: jest.fn().mockName('createRoot')
}));
let documentSpy=jest.spyOn(document, 'getElementById')
describe('Entry point index test', () => {
const doc =document.createElement('div');
doc.setAttribute('id', 'root');
beforeEach(() => {
ReactDOM.createRoot.mockReturnValue({render});
require("../index.js");
});
it('should call ReactDOM.createRoot once', () => {
expect(ReactDOM.createRoot).toHaveBeenCalledTimes(1);
});
it('should call document.getElementById with root once', () => {
expect(documentSpy).toHaveBeenCalledTimes(1);
expect(documentSpy).toHaveBeenCalledWith('root');
});
it('should call render with React.StrictMode', () => {
expect(render).toHaveBeenCalledTimes(1);
expect(render).toHaveBeenCalledWith( <React.StrictMode />,);
});
});
Here what i did and looks like it works just perfect (100% coverage, app doesn't break):
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
export const ReactStrictMode = <React.StrictMode>
<App />
</React.StrictMode>
export const rootElement = document.getElementById('root')
ReactDOM.render(
ReactStrictMode,
rootElement
);
and then in index.spec.js:
// src/index.spec.js
/* eslint-env jest */
import React from 'react'
import ReactDOM from 'react-dom'
import { ReactStrictMode, rootElement } from './index'
jest.mock('react-dom', () => ({ render: jest.fn() }))
describe('index.js', () => {
it('renders without crashing', () => {
ReactDOM.render(ReactStrictMode, rootElement)
expect(ReactDOM.render).toHaveBeenCalledWith(ReactStrictMode, rootElement)
})
})
Added a couple of more test cases. Any feedback would be appreciated...
import React from "react";
import { render, cleanup } from "#testing-library/react";
import ReactDOM from "react-dom";
import App from "./App";
afterEach(cleanup);
// jest.mock will mock all the function using jest.fn() that is present inside the react-dom library
jest.mock("react-dom");
describe("Testing Application Root", () => {
it("should render without crashing", () => {
const div = document.createElement("div");
div.id = "root";
document.body.appendChild(div);
require("./index");
expect(ReactDOM.render).toHaveBeenCalledWith(<App />, div);
});
it("should render the app inside div which has root id", () => {
expect(global.document.getElementById("root")).toBeDefined();
});
it("should render App component", () => {
expect(App).toBeDefined();
});
});

Resources