Skip to main content
react-md

Quickstart

ReactMD provides built-in support for testing for the following test frameworks:

The test environment is handled through:

Installing Dependencies

First, install jest or vitest along with the React Testing Library packages:

npm install --save-dev \
  jest \
  @jest/globals \
  @jest/types \
  @testing-library/dom \
  @testing-library/react \
  @testing-library/jest-dom \
  @testing-library/user-event
pnpm add --save-dev \
  jest \
  @jest/globals \
  @jest/types \
  @testing-library/dom \
  @testing-library/react \
  @testing-library/jest-dom \
  @testing-library/user-event
yarn add --save-dev \
  jest \
  @jest/globals \
  @jest/types \
  @testing-library/dom \
  @testing-library/react \
  @testing-library/jest-dom \
  @testing-library/user-event

Setup Polyfills and Test Matchers

Next, create a src/testSetup.ts that imports the polyfills and additional React Testing Library matchers:

src/testSetup.ts
import "@react-md/core/test-utils/polyfills";
import "@react-md/core/test-utils/jest-globals/setup";

// Optional: allow data-testid to be a valid DOM property for typescript
import "@react-md/core/test-utils/data-testid";

Then configure the jest setupFilesAfterEnv or vitest setupFiles to include the src/testSetup.ts so it is included before each test.

jest.config.ts
 import { type Config } from "jest";

 const config: Config = {
   testEnvironment: "jsdom",
+  setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
 };

 export default config;

Default Test Behavior

To make testing easier, the @react-md/core/test-utils/jest-globals/setup and @react-md/core/test-utils/vitest/setup add the following code:

beforeEach(() => {
  // set the mode to `none` in tests since ripples require
  // `getBoundingClientRect()` to create correct CSS. You'll either see warnings
  // in the console around invalid css values or `NaN`.
  INTERACTION_CONFIG.mode = "none";

  // disable transitions in tests since it just makes it more difficult
  TRANSITION_CONFIG.disabled = true;
});

If a specific test needs to verify the interaction mode or transition behavior, that specific test can just set the expected state instead using .spyOn():

src/__tests__/SomeComponent.tsx
const modeMock = jest
  .spyOn(INTERACTION_CONFIG, "mode", "get")
  .mockReturnValue("press");

const transitionMock = jest
  .spyOn(TRANSITION_CONFIG, "disabled", "get")
  .mockReturnValue(false);

Render Tests with rmdRender

It is recommended to jump to the next section to create a custom test renderer for larger apps.

Now replace all @testing-library/react imports with @react-md/core/test-utils and any render with rmdRender:

src/__tests__/App.tsx
-import { render, userEvent } from "@testing-library/react";
+import { rmdRender, userEvent } from "@react-md/core/test-utils";

 import App from "../App.jsx";

 describe('App', () => {
   it('should render without crashing', () => {
-    expect(() => render(<App />)).not.toThrow();
+    expect(() => rmdRender(<App />)).not.toThrow();
   });
 });

The rmdRender function just automatically wraps each test in the CoreProviders to prevent any errors around missing context providers.

Create a Custom Test Renderer

My preferred method of making all the global context providers, data stores, react-md configuration, etc available for each test is to create a utility file that re-exports everything from @react-md/core/test-utils, @testing-library/react, and @testing-library/user-event. The example below shows a possible setup.

See Custom Renderer for additional context.

src/__tests__/MyComponent.tsx
-import { render, userEvent } from "@react-md/core/test-utils";
+import { render, userEvent } from "../test-utils";
src/rmdConfig.tsx
import { type ReactMDCoreConfiguration } from "@react-md/core/CoreProviders";
import { configureIcons } from "@react-md/core/icon/config";

// any icon overrides. Using material icons as an example
configureIcons({
  back: <KeyboardArrowLeftIcon />,
  clear: <ClearIcon />,
  close: <CloseIcon />,
  checkbox: <CheckBoxOutlineBlankIcon />,
  checkboxChecked: <CheckBoxIcon />,
  checkboxIndeterminate: <IndeterminateCheckBoxIcon />,
  dropdown: <ArrowDropDownIcon />,
  error: <ErrorOutlineIcon />,
  expander: <KeyboardArrowDownIcon />,
  forward: <KeyboardArrowRightIcon />,
  menu: <MenuIcon />,
  notification: <NotificationsIcon />,
  password: <RemoveRedEyeIcon />,
  radio: <RadioButtonUncheckedIcon />,
  radioChecked: <RadioButtonCheckedIcon />,
  remove: <CancelIcon />,
  selected: <CheckIcon />,
  sort: <ArrowUpwardIcon />,
  upload: <FileUploadIcon />,
});

export const rmdConfig: ReactMDCoreConfiguration = {
  // any other global changes
  // ssr: true,
};
src/test-utils.tsx
import { Fragment, type ReactElement } from "react";
import {
  rmdRender,
  type ReactMDRenderOptions,
  type RenderResult,
} from "@react-md/core/test-utils";

import { rmdConfig } from "./rmdConfig.jsx";
import { MyCustomProviders } from "./MyCustomProviders.jsx";

export * from "@react-md/core/test-utils";
export * from "@react-md/core/test-utils/jest-globals";

export const render = (
  ui: ReactElement,
  { wrapper: Wrapper = Fragment, ...options }: ReactMDRenderOptions = {}
): RenderResult => {
  return rmdRender(ui, {
    ...options,
    rmdConfig: {
      ...rmdConfig,
      ...options.rmdConfig,
    },
    wrapper: ({ children }) => (
      <MyCustomProviders>
        <Wrapper>{children}</Wrapper>
      </MyCustomProviders>
    ),
  });
};