Vitest vs node:test: Qual Test Runner JS?
O Vitest é um framework de testes rápido movido a Vite com recursos ricos; o node:test é o runner nativo, sem dependências, que vem com o Node.
O Vitest reaproveita sua configuração e transforms do Vite, roda testes em paralelo com watch mode e oferece uma API parecida com a do Jest, mocking, coverage e testes de snapshot - ideal para projetos Vite/TS. O node:test é embutido no Node moderno, não precisa de dependências e cobre as necessidades centrais (test, subtests, asserções via node:assert, mocking básico), mas é mais minimalista. O Vitest é rico em recursos; o node:test é enxuto e sem dependências.
What each one actually supports in 2026
| Vitest | node:test | |
|---|---|---|
| Dependências | Vitest + Vite | Nenhuma (embutido) |
| Recursos | Mocks, snapshots, coverage | Testes essenciais |
| TS / Vite | De primeira classe | Transform manual |
| Watch mode | HMR rápido | Básico |
| Melhor para | Apps Vite/TS | Testes enxutos, sem dependências |
| Watch mode | Sim (flag experimental) | Sim, reexecução no estilo HMR |
| TypeScript | Remoção nativa de tipos | Direto, via Vite |
| JSX e testes de componente | Não | Vue, React, Svelte e outros |
| Modo navegador | Não | Sim |
| Reporters de CI | spec, tap, dot, junit, lcov embutidos | Vários, mais sharding para CI distribuído |
| Benchmark | Não | Sim, via Tinybench |
| Teste de tipos | Não | Sim, via expect-type |
| Tags de teste | Sim, desde o Node 26.2.0 | Via filtragem |
No CI
O Vitest encaixa em projetos já em Vite ou que querem um runner completo com coverage e snapshots; ele paraleliza bem no CI. O node:test é atraente para bibliotecas que não querem dependências de teste e querem inicialização rápida, embora você cuide dos transforms de TS e do coverage (via c8) por conta própria. Escolha Vitest pela profundidade de recursos em apps, node:test pela pegada mínima.
--test-reporter=junitand=lcovare built in, so CI test reporting and coverage upload need no extra packages.--test-concurrency=Ncontrols parallel test files; concurrency is not an afterthought.--test-randomizewith--test-random-seedmakes order-dependence reproducible instead of mysterious.--test-rerun-failures <state-file>reruns only what failed, which is a meaningful CI optimisation.--experimental-test-coverageproduces V8 coverage without instrumenting your build.
import { test, describe, it, mock, snapshot } from 'node:test';
import assert from 'node:assert';
// module mocking, not just function mocking
mock.module('./payments.js', {
namedExports: { charge: mock.fn(() => ({ ok: true })) },
});
// timer mocking
mock.timers.enable({ apis: ['setTimeout', 'Date'] });
describe('checkout', () => {
it('charges once', async (t) => {
const spy = t.mock.method(cart, 'total');
await checkout(cart);
assert.strictEqual(spy.mock.callCount(), 1);
});
});Acelere
Faça cache das dependências e distribua os testes em shards entre jobs para reduzir o tempo total. Ambos rodam em runners de CI; runners gerenciados mais rápidos encurtam as etapas de execução de testes e transform.
- Component testing. Vue, React, Svelte and other component tests need a transform pipeline and a DOM. Vitest inherits both from Vite; node:test has neither.
- Browser mode. Running the suite in a real browser rather than a simulated DOM has no built-in equivalent.
- JSX and framework transforms. Vitest reuses your existing Vite config, resolvers, and plugins, so tests see the same module graph as your app. Reproducing that under node:test means building it yourself.
- Benchmarking and type testing. Tinybench-backed benchmarks and expect-type assertions have no built-in counterpart.
- Sharding. Vitest supports splitting a suite across CI machines natively, which matters once a suite is long enough to need it.
- Jest compatibility. The Jest-compatible
expectand mocking API makes migrating an existing Jest suite mostly mechanical.
A regra de decisão: o que você está testando?
A divisão honesta não é recursos contra dependências. É se os seus testes precisam de um ambiente com formato de navegador e de um transform de build.
| O que você está testando | Escolha | Por quê |
|---|---|---|
| Uma biblioteca npm publicada | node:test | Zero dependências no caminho de teste, e nada para manter em cadência de release |
| Um serviço de backend ou CLI | node:test | Sem DOM, sem transforms; a superfície embutida dá conta |
| Um app React, Vue ou Svelte | Vitest | Testes de componente precisam do pipeline de transform do Vite e de um DOM |
| Qualquer coisa já em Vite | Vitest | A configuração de teste é a do seu app; sem um segundo grafo de módulos para manter |
| Uma suíte Jest que você quer migrar | Vitest | expect e mocks compatíveis com Jest tornam o trabalho quase mecânico |
| Uma suíte que precisa de benchmarks ou testes de tipo | Vitest | Não existe equivalente embutido |
| Um monorepo com os dois | Ambos | A escolha do runner é por pacote; não há regra contra misturar |
Custo de inicialização e tempo de CI
O argumento prático para o runner embutido no CI é que não há nada a instalar antes de rodar os testes. Em um job de CI frio, o npm ci de um framework de teste e da sua cadeia de transform é tempo de relógio real a cada execução, gasto antes de uma única asserção rodar.
- node:test não adiciona nada para instalar nem para resolver;
node --testroda contra o Node que já está no runner. - O Vitest puxa Vitest mais Vite mais as dependências transitivas, e aquece um pipeline de transform na primeira execução.
- Em contrapartida, o sharding do Vitest pode cortar o tempo total de relógio de uma suíte grande muito mais do que o tempo de instalação custa.
Rodando cada um no CI
# node:test - nothing to install
- run: node --test --experimental-test-coverage \
--test-reporter=junit --test-reporter-destination=junit.xml \
--test-reporter=lcov --test-reporter-destination=lcov.info
# Vitest
- run: npm ci
- run: npx vitest run --coverage --reporter=junit --outputFile=junit.xml
# Vitest, sharded across 4 machines
- run: npx vitest run --shard=${{ matrix.shard }}/4Dá para migrar entre eles?
Em parte, e a direção importa. Ir do Vitest para o node:test é mecânico para testes unitários simples: o formato describe/it é o mesmo, e o trabalho principal é reescrever asserções expect(...) para node:assert e remapear chamadas de mock. Migrar testes de componente não é reescrita, é reconstrução, porque o pipeline de transform e o DOM não têm equivalente embutido.
- Asserções:
expect(a).toBe(b)viraassert.strictEqual(a, b). Quase tudo localizar e substituir, com cuidado na igualdade profunda. - Mocks:
vi.fn()viramock.fn();vi.mock()viramock.module(), cuja API não é idêntica. - Snapshots: os dois têm, mas os formatos de arquivo diferem, então conte com regenerar em vez de portar.
- Testes de componente: não há caminho. Estes ficam no Vitest.
The switching cost is mostly in the parts nobody lists
- Assertions and mocks usually port mechanically when the target implements a compatible API; custom transformers and framework plugins do not.
- Snapshot formats differ between runners, so plan to regenerate and review rather than port.
- Run both suites in parallel in CI for a period and diff the results. A migration that changes which tests fail is not a migration, it is a regression you have not found yet.
- Coverage numbers move on a runner change even when the tests do not, because instrumentation differs. Re-baseline any coverage gate deliberately.
The verdict
Construir um app Vite ou TypeScript e querer mocks, snapshots e coverage prontos de fábrica: Vitest. Escrever uma biblioteca que valoriza zero dependências e inicialização rápida: node:test. A maioria dos times de app escolhe Vitest; minimalistas e autores de libs cada vez mais usam node:test.
Perguntas frequentes
O test runner embutido do Node está pronto para produção?
node:test está marcado como Stable desde o Node 20, não experimental. O teste de snapshot estabilizou na v22.3.0 e o setup e teardown globais chegaram na v24. A única parte ainda atrás de uma flag experimental é a cobertura de código, via --experimental-test-coverage.O node:test suporta mocking?
mock.module(), além de timers incluindo setTimeout, setInterval e Date por meio de mock.timers.O node:test roda TypeScript?
--no-strip-types estende os padrões padrão de arquivo de teste para incluir .ts, .mts e .cts. Você não precisa de um passo de transform separado para TypeScript direto, embora qualquer coisa que dependa de plugins do Vite ou aliases de caminho ainda exija configuração.O Vitest é mais rápido que o node:test?
Posso usar node:test para testes de componente React?
O node:test funciona com ferramentas de relatório e cobertura de CI?
junit e lcov, então JUnit XML para relatório de testes e lcov para upload de cobertura funcionam sem pacotes adicionais. Isso remove o que era o argumento mais forte contra ele no CI.Como migro do Vitest para o node:test?
describe/it é aproveitada, asserções expect(...) viram chamadas de node:assert, e vi.fn()/vi.mock() viram mock.fn()/mock.module(). Os formatos de snapshot diferem, então planeje regenerá-los. Testes de componente não migram e devem ficar no Vitest.