Test Runner Nativo do Node.js: Testes sem Jest nem Mocha

Como usar o módulo node:test, estável desde o Node.js 20, para escrever testes com describe/it, hooks, mocks, reporters e modo watch sem instalar nenhuma dependência.

Durante anos, escrever testes em Node.js significava instalar um framework externo — Jest, Mocha, Vitest — antes da primeira linha de teste. Desde o Node.js 20, o próprio runtime traz um test runner nativo e estável no módulo node:test: suítes, hooks, mocks, relatórios e modo watch, tudo sem nenhuma dependência no package.json.

O primeiro teste

Basta importar test de node:test e usar o node:assert/strict para as asserções. O arquivo abaixo testa uma função simples de soma:

// soma.js
export function soma(a, b) {
    return a + b;
}
// soma.test.js
import test from 'node:test';
import assert from 'node:assert/strict';
import { soma } from './soma.js';

test('soma dois números', () => {
    assert.equal(soma(2, 3), 5);
});

test('funciona com negativos', () => {
    assert.equal(soma(-4, 1), -3);
});

Para rodar, use a flag --test. Sem argumentos, o Node procura automaticamente arquivos com nomes como *.test.js, *-test.js, *_test.js, test-*.js ou qualquer arquivo dentro de uma pasta test/:

node --test

Um script no package.json deixa o comando com a cara de sempre:

{
  "type": "module",
  "scripts": {
    "test": "node --test"
  }
}

describe, it e hooks

Quem vem do Jest ou do Mocha encontra a mesma API: describe() e it() são aliases de suite() e test(), e os hooks before(), after(), beforeEach() e afterEach() preparam e limpam o estado entre os testes:

import { describe, it, beforeEach } from 'node:test';
import assert from 'node:assert/strict';

class Carrinho {
    itens = [];
    adicionar(produto, preco) {
        this.itens.push({ produto, preco });
    }
    total() {
        return this.itens.reduce((soma, item) => soma + item.preco, 0);
    }
}

describe('Carrinho', () => {
    let carrinho;

    beforeEach(() => {
        carrinho = new Carrinho();
    });

    it('começa vazio', () => {
        assert.equal(carrinho.total(), 0);
    });

    it('soma o preço dos itens', () => {
        carrinho.adicionar('Teclado', 150);
        carrinho.adicionar('Mouse', 50);
        assert.equal(carrinho.total(), 200);
    });
});

Testes assíncronos funcionam com async/await sem nenhuma configuração extra — se a Promise rejeitar, o teste falha.

Mocks sem biblioteca externa

O objeto mock cria funções espiãs e substitui métodos de objetos. Cada mock registra quantas vezes foi chamado, com quais argumentos e o que retornou:

import { test, mock } from 'node:test';
import assert from 'node:assert/strict';

async function notificar(usuario, mailer) {
    if (!usuario.ativo) return false;
    await mailer.enviar(usuario.email, 'Bem-vindo!');
    return true;
}

test('envia e-mail só para usuários ativos', async () => {
    const mailer = { enviar: mock.fn(async () => 'ok') };

    await notificar({ ativo: true, email: 'ana@site.com' }, mailer);
    await notificar({ ativo: false, email: 'bruno@site.com' }, mailer);

    assert.equal(mailer.enviar.mock.callCount(), 1);
    assert.deepEqual(mailer.enviar.mock.calls[0].arguments, ['ana@site.com', 'Bem-vindo!']);
});

Dentro de um teste, t.mock.method(objeto, 'metodo') espiona um método existente e é restaurado automaticamente quando o teste termina. Versões recentes também oferecem mock.timers, que controla setTimeout e companhia para testar código baseado em tempo sem esperar de verdade.

Pular, marcar como pendente e focar

As opções skip, todo e only controlam quais testes rodam:

import test from 'node:test';

test('integração com o gateway de pagamento', { skip: 'sem credenciais no CI' }, () => {
    // nunca executado
});

test('exportar relatório em PDF', { todo: true }, () => {
    // executado, mas uma falha aqui não quebra a suíte
});

test('cálculo de frete', { only: true }, () => {
    // com --test-only, só os testes marcados com only rodam
});

Reporters, cobertura e watch

O formato de saída padrão é o spec, legível para humanos. Para CI, há reporters prontos como tap, dot, junit e lcov:

# saída compacta
node --test --test-reporter=dot

# XML no formato jUnit, para o CI ler os resultados
node --test --test-reporter=junit --test-reporter-destination=resultado.xml

# relatório de cobertura de código (ainda experimental)
node --test --experimental-test-coverage

# roda os testes de novo a cada alteração nos arquivos
node --test --watch

Quando usar

Cuidados

O test runner cobre o essencial, mas não replica tudo do Jest: não há snapshot de componentes de UI nem ambiente jsdom embutido, então projetos de front-end costumam continuar com Vitest ou Jest. A cobertura de código e o modo watch do test runner ainda são marcados como experimentais na documentação, e os padrões de descoberta de arquivos mudaram entre versões — prefira uma versão LTS recente.

Compatibilidade

O módulo node:test chegou no Node.js 18 como experimental e se tornou estável no Node.js 20. Qualquer versão LTS a partir da 20 já traz test(), describe()/it(), hooks e mocks sem flag nenhuma.

documentação oficial

Conclusão

O test runner nativo tira do caminho a decisão "qual framework instalar" para a maioria dos projetos Node.js. Com node --test, describe/it, hooks e mocks já no runtime, dá para começar a testar com zero dependências e migrar para algo mais pesado só quando realmente precisar.

Tecnologia:
node

Deixe seu Comentário!

Dúvida, sugestão ou só um "valeu!" — tudo é bem-vindo por aqui.

Comentário
Nome
Email
lock Seu e-mail não será publicado.