Jest

Jest 62,362 is the runner most React 7,897 suites were written against. Facebook released it in 2014 and donated it to the OpenJS Foundation in 2022. Jest 30.5.1 is current, and you will meet it in older codebases and in React Native 36,878 , where it remains the default. The React-specific work is identical in both runners — render, screen, getByRole, userEvent; what differs is the machinery.

The two mainstream React runners compared
Vitest 5.0.1 102,989 Jest 30.5.1
Transform your Vite 25,978 config Babel 41,624 or SWC 553,439 , configured separately
ES modules native native, or transpiled to CommonJS
Mock API vi.fn, vi.mock, vi.spyOn jest.fn, jest.mock, jest.spyOn
Watch mode Vite module graph file-system watcher

The overlap is deliberate — Vitest set out to be a drop-in replacement — so a Jest test usually runs under Vitest once vi replaces jest. Setting Jest up by hand takes three files, because it has no bundler behind it.

jest.config.jsShell
export default {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
  moduleNameMapper: { '\\.(css|less|scss)$': 'identity-obj-proxy' },
};

moduleNameMapper exists because Node cannot import './Button.css'; every non-JavaScript import needs a stub such as identity-obj-proxy. jsdom 21,691 moved out of the Jest package in version 28, so jest-environment-jsdom must be installed separately — omitting it produces "Test environment jsdom cannot be found". The JSX transform comes from a third file, babel.config.js, with @babel/preset-react in runtime: 'automatic' mode.

Choose Jest for React Native or for a large suite whose custom transformers would have to be ported, Vitest for anything built on Vite. Never both in one package: two expect implementations in one process produce errors naming matchers you never wrote.