ReactMD provides built-in support for testing for the following test frameworks:
The test environment is handled through:
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
Next, create a src/testSetup.ts
that imports the polyfills and additional
React Testing Library matchers:
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;
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()
:
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.
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 "@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";
const modeMock = jest
.spyOn(INTERACTION_CONFIG, "mode", "get")
.mockReturnValue("press");
const transitionMock = jest
.spyOn(TRANSITION_CONFIG, "disabled", "get")
.mockReturnValue(false);
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>
),
});
};