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:
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.
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()
:
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
:
-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.
-import { render, userEvent } from "@react-md/core/test-utils";
+import { render, userEvent } from "../test-utils";
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,
};
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>
),
});
};