Recently, I continued the development of the previous Kindle UI library and thought it was time to write some tests, so I decided to use the testing solution provided by Mocha (to try something new).
(During development, ensuring code quality and correctness is very important. Testing is one effective way to achieve this, especially in JavaScript development. In React application development, Mocha and Chai are two very popular testing frameworks. This article will introduce how to test React applications using Mocha and Chai.)
Why Not Use Jest, the Official React Testing Recommendation?
Jest was originally designed for testing React apps. Compared to Jest, Mocha offers greater flexibility (the latter requires additional configuration, while the former works out of the box), can run in both browser and Node environments, and supports some complex statements such as:
expect(person.age).to.be.lte(35).and.to.be.gte(18);
Since my project is not a single React app but a monorepo, using Mocha at the workspace root is undoubtedly a better choice.
Installing Dependencies
Mocha itself does not support JSX, so we need to install some dependencies:
yarn add mocha -D # Babel related plugins yarn add -W -D @babel/preset-env @babel/preset-react @babel/preset-typescript @babel/register # This dependency is used for rendering React, please choose the version according to your React version! yarn add react-test-renderer@17.0.2 -D -W
Note: If you install testing dependencies at the workspace root (recommended practice), remember to add the -W flag.
Configuring Mocha
Create a .mocharc.js file in the project root directory with the following content:
module.exports = { extension: ["js", "mjs", "ts", "tsx"], ignore: ["**/build/**", "**/node_modules/**"], recursive: true, timeout: (process.env.CIRCLECI === "true" ? 5 : 2) * 1000, // Circle CI has low-performance CPUs. reporter: "dot", require: [require.resolve("./test/utils/setupBabel")], "watch-ignore": [ ".git", "**/node_modules/**", "**/build/**", "docs/.next/**", ], };
In the /test/utils/setupBabel directory, create a setupBabel.js file:
require("@babel/register")({ extensions: [".js", ".ts", ".tsx"], });
Configuring Babel
With Babel, we can use CJS format test files without the annoying "type": "module" setting.
// babel.config.js module.exports = function getBabelConfig(api) { const useESModules = api.env(["legacy", "modern", "stable", "rollup"]); const presets = [ [ "@babel/preset-env", { browserslistEnv: process.env.BABEL_ENV || process.env.NODE_ENV, modules: useESModules ? false : "commonjs", }, ], [ "@babel/preset-react", { runtime: "automatic", }, ], "@babel/preset-typescript", ]; const plugins = []; return { assumptions: { noDocumentAll: true, }, presets, plugins, ignore: [/@babel[\\\\|/]runtime/], // Fix a Windows issue. overrides: [ { exclude: /\\.test\\.(js|ts|tsx)$/, plugins: ["@babel/plugin-transform-react-constant-elements"], }, ], }; };
Diff Example
Here is what a config change looks like:
module.exports = { extension: ["js", "mjs", "ts", "tsx"], - ignore: ["**/build/**"], + ignore: ["**/build/**", "**/node_modules/**"], recursive: true, - timeout: 2000, + timeout: (process.env.CIRCLECI === "true" ? 5 : 2) * 1000, reporter: "dot", + require: [require.resolve("./test/utils/setupBabel")], };
Testing
Add a shortcut script in package.json, please configure according to your actual project:
"test:unit": "cross-env NODE_ENV=test mocha --config .mocharc.js 'packages/kindle-ui/**/*.test.{mjs,js,ts,tsx}' 'test/utils/**/*.test.{js,ts,tsx}'"
After that, we can happily write tests.
For more usage of react-test-renderer, you can refer to the official documentation.
Here are some common testing scenarios for reference:
Checking Component Type
import * as React from "react"; import { expect } from "chai"; import renderer from "react-test-renderer"; // It is recommended to use the package after the link to ensure it is close to real scenarios. import { ListItem } from "@kindle-ui/core"; describe("<ListItem />", () => { describe("prop: component", () => { it("renders a div", () => { const component = renderer.create(<ListItem />); expect(component.toTree().rendered).to.have.property("type", "div"); }); it("renders a link", () => { const component = renderer.create( <ListItem component="a" href="#" /> ); expect(component.toTree().rendered).to.have.property("type", "a"); }); }); });
Checking Render Results
it("render in Container", () => { expect(() => renderer.create( <Container> <ListItem>test</ListItem> </Container> ) ).not.to.throw(); });
Improvements
The testing library provided by React has limited functionality; for example, we cannot test whether an element is visible or simulate user interactions with the page.
Therefore, we can use @testing-library/react for further improvements.
Note that there is no document object in the Node environment, so we need to use the JSDom library to simulate one.
First, add hooks for Mocha:
// .mocharc.js module.exports = { {/**... */} beforeEach: () => { const dom = new JSDOM("", { pretendToBeVisual: true, url: "<http://localhost>", }); global.window = dom.window; }, };
Update the test code:
import { queries, within } from "@testing-library/react/pure"; describe("general rendering", async () => { it("render in Container", async () => { render(<ListItem>KindleUI</ListItem>); expect( within(document.body, { ...queries }) .getByRole("ListItem") .toHaveTextContent("KindleUI") ).to.be(true); }); });
