Using the node 18+ native test runner with TypeScript and React
Node.js 18 introduced a native test runner that eliminates the need for jest, mocha or vitest in basic scenarios. We’ve been using it exclusively for the past couple of weeks with great success.
Configuration
The test script in package.json:
{
"scripts": {
"test": "node --import global-jsdom/register --import tsx --experimental-transform-types --experimental-test-module-mocks --test"
}
}
Flag breakdown:
--import tsx
: Handles TypeScript compilation--import global-jsdom/register
: Provides DOM environment for React components--experimental-transform-types
: Strips TypeScript types during execution--experimental-test-module-mocks
: Enables module mocking--test
: Activates Node’s test runner
Dependencies
Required dev dependencies:
pnpm add --save-dev @testing-library/react global-jsdom jsdom tsx
TypeScript Unit Tests
import { describe, test } from "node:test";
import { strict as assert } from "node:assert";
import { getInitials } from "./stringUtils";
describe("String Utils", () => {
test("should return correct initials", () => {
const result = getInitials("John Smith");
assert.equal(result, "JS");
});
});
Uses Node’s built-in assert module for assertions.
React Component Testing
React Testing Library can still work with jsdom to provide a DOM.
import React from "react";
import { describe, test } from "node:test";
import { strict as assert } from "node:assert";
import { render, fireEvent, screen, cleanup } from "@testing-library/react";
import { Toggle } from "./Toggle";
beforeEach(() => {
cleanup(); // cleans the dom state between each test
});
describe("Toggle", () => {
test("calls onToggleClick when clicked", () => {
let callCount = 0;
const { getByText } = render(
<Toggle
options={["On", "Off"]}
selected="On"
onToggleClick={() => callCount++}
/>
);
fireEvent.click(getByText("On"));
assert.equal(callCount, 1);
});
});
Running Tests
# All tests
pnpm test
# Watch mode
pnpm test -- --watch
# Specific file
node --import global-jsdom/register --import tsx --experimental-transform-types --test src/components/Toggle.test.tsx
Comparison
Advantages:
- No configuration files required
- Direct TypeScript execution
- Built into Node.js runtime
- Smaller dependency footprint
Limitations:
- Requires Node 18+
- Uses experimental flags
- Smaller ecosystem than Jest, vitest, etc. This means fewer examples