# Vitest vs node:test: Qual Test Runner JS?

> Vitest vs o test runner nativo do Node: um framework rápido movido a Vite vs o node:test sem dependências. Recursos, velocidade e adequação a CI comparados.

Source: https://latchkey.dev/pt/learn/tool-comparisons/vitest-vs-node-test  
Updated: 2026-08-20

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.

## Comparison

|  | 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=junit` and `=lcov` are built in, so CI test reporting and coverage upload need no extra packages.
- `--test-concurrency=N` controls parallel test files; concurrency is not an afterthought.
- `--test-randomize` with `--test-random-seed` makes order-dependence reproducible instead of mysterious.
- `--test-rerun-failures <state-file>` reruns only what failed, which is a meaningful CI optimisation.
- `--experimental-test-coverage` produces V8 coverage without instrumenting your build.

```node:test, current capabilities
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);
  });
});
```

> Coverage is the one headline feature still behind an experimental flag. It works and it emits lcov, but if your organisation forbids experimental flags in CI, that single constraint may decide this comparison for you.

## 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 `expect` and mocking API makes migrating an existing Jest suite mostly mechanical.

> Notice what is not on this list: mocking, snapshots, watch, concurrency, and CI reporters. Those used to be the argument for reaching for a framework and they no longer are.

## 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 --test` roda 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.

> Esse trade-off inverte com o tamanho da suíte. Para uma biblioteca pequena a instalação domina e o node:test vence no tempo total de CI. Para uma suíte grande de aplicação a execução domina e o sharding do Vitest vence. Meça a sua própria divisão entre instalar e executar antes de presumir qualquer um dos dois.

## Rodando cada um no CI

```GitHub Actions steps
# 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 }}/4
```

> Os dois emitem JUnit XML e lcov, então relatório de testes, upload de cobertura e anotações em PR funcionam igual de qualquer forma. Isso já foi motivo para evitar o runner embutido e não é mais.

## Dá 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)` vira `assert.strictEqual(a, b)`. Quase tudo localizar e substituir, com cuidado na igualdade profunda.
- Mocks: `vi.fn()` vira `mock.fn()`; `vi.mock()` vira `mock.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.

## O veredito

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.

## FAQ

### O test runner embutido do Node está pronto para produção?

Sim. 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?

Sim, e de forma mais completa do que a maioria das comparações sugere. Ele faz mock de funções, métodos, propriedades, getters e setters, e de módulos inteiros via `mock.module()`, além de timers incluindo `setTimeout`, `setInterval` e `Date` por meio de `mock.timers`.

### O node:test roda TypeScript?

Sim. O Node remove tipos nativamente, e passar `--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?

Depende de que parte você mede. O node:test evita instalar e resolver um framework, então em suítes pequenas costuma vencer no tempo total de relógio do CI. O Vitest suporta sharding entre máquinas, o que vence de forma decisiva assim que a suíte fica grande. Meça a sua divisão entre instalar e executar em vez de presumir.

### Posso usar node:test para testes de componente React?

Na prática não. Testar componentes exige um DOM e um pipeline de transform de JSX, e o node:test não fornece nenhum dos dois. O Vitest herda ambos do Vite. Se você testa componentes, só isso já decide a comparação.

### O node:test funciona com ferramentas de relatório e cobertura de CI?

Sim. Ele traz os reporters `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?

Para testes unitários simples é quase todo mecânico: a estrutura `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.

### Autores de biblioteca devem usar Vitest ou node:test?

node:test, na maioria dos casos. Uma biblioteca publicada se beneficia de não ter nada no caminho de teste para manter atualizado, nenhuma cadeia de transform para depurar contra as configurações dos consumidores, e nenhuma cadência de release de framework para acompanhar. O runner embutido hoje cobre o que suítes de teste de biblioteca normalmente precisam.

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
