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();
});
});
Related
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.
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'
I'm getting error message Target container is not a DOM element. Using webpack.
Full Error:
FAIL src/App.test.js
● Test suite failed to run
Invariant Violation: Target container is not a DOM element.
at invariant (node_modules/fbjs/lib/invariant.js:42:15)
at legacyRenderSubtreeIntoContainer (node_modules/react-dom/cjs/react-dom.development.js:17238:34)
at Object.render (node_modules/react-dom/cjs/react-dom.development.js:17317:12)
at Object.<anonymous> (src/index.js:32:20)
at Object.<anonymous> (src/App.test.js:5:14)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:169:7)
App.js
import React, { Component } from 'react';
import './App.scss';
import Tables from './containers/Tables/Tables'
class App extends Component {
render() {
return (
<Tables />
);
}
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { combineReducers, createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import aaaApi from '#aaa/aaajs';
import './index.css';
import App from './App';
import tableBuilderReducer from './store/reducers/tableBuilder'
import registerServiceWorker from './registerServiceWorker';
const aaa = new aaaApi('xxx');
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const reducers = {
platforms: tableBuilderReducer('platforms'),
regions: tableBuilderReducer('regions'),
playback: tableBuilderReducer('playback')}
export const store = createStore(
combineReducers(reducers),
composeEnhancers(
applyMiddleware(thunk.withExtraArgument(aaa)),
),
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
registerServiceWorker();
App.test.js
import React from 'react'
import Enzyme, { mount, shallow } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import App from './App'
import { store } from './index.js'
Enzyme.configure({ adapter: new Adapter() })
function setup() {
const props = {
sortColumnHandler: jest.fn()
}
const enzymeWraper = mount(<App />)
return {
props,
enzymeWraper
}
}
describe('components', ()=> {
describe('Tables', () => {
it('should render self and sub-components', () => {
const { enzymeWraper } = setup()
})
})
})
The issue is being caused by index.js being included in the test and is failing at ReactDOM.render() when root is not found in the document created by the jsdom environment in Jest.
import { store } from './index.js' is not needed in App.test.js so removing that will fix the initial error.
Using mount from enzyme does a full DOM rendering and requires that everything be set up for a full render. It should only be used to unit test components that use lifecycle methods, interact with the DOM, etc. It will fully render the component being tested and all children components. In this case the children of App use the redux store so a store would need to be provided in a wrapping Provider in order to do a full render using mount.
In most cases, React components should be unit tested using shallow. It does a shallow rendering of just the component being tested. In this case App is a simple stateless component that contains Tables so using shallow is the right approach.
bonus tip:
Install enzyme-to-json as a dev dependency, add it to Jest as a snapshot serializer, and App.test.js gets simplified to this:
import { shallow } from 'enzyme';
import * as React from 'react';
describe("App", () => {
it('renders as expected', () => {
const component = shallow(<App />);
expect(component).toMatchSnapshot();
});
});
Trying to test my react-native app with jest and enzyme as follows.
import 'react-native';
import React from 'react';
import renderer from 'react-test-renderer';
import { shallow } from 'enzyme';
import { SignUp } from '../../src/pages';
describe('Testing SignUp component', () => {
it('renders as expected', () => {
const wrapper = shallow(
<SignUp />
);
expect(wrapper).toMatchSnapshot();
});
});
on running npm test I get error Cannot find module 'enzyme/build/ShallowTraversal' from 'shallow.js'
test suite failed to run.
Please note that test done minus enzyme runs properly
test('SignUp Page renders correctly', () => {
const tree = renderer.create(<SignUp />).toJSON();
expect(tree).toMatchSnapshot();
});
What could be wrong?
Just solved the problem by first installing enzyme-to-json; npm install --save-dev enzyme-to-json
imported toJson inside my test_file.js
import 'react-native';
import React from 'react';
import renderer from 'react-test-renderer';
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json'; //added this line
changed the test to this
describe('Testing SignUp component', () => {
it('renders as expected', () => {
const wrapper = shallow(
<SignUp />
);
expect(toJson(wrapper)).toMatchSnapshot(); //edited this line
});
});
changed snapShotSerializers in package.json
"preset": "react-native",
"collectCoverage": true,
"collectCoverageFrom": [
"**/src/**.{js,jsx}"
],
"snapshotSerializers": ["enzyme-to-json/serializer"] //added this line
Now my tests are running correctly.
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