Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

fast-check no Node.js

Atualizado em: 26 de setembro de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

fast-check é uma biblioteca de property-based testing para JavaScript e TypeScript. Em vez de escrever somente exemplos fixos, o teste descreve propriedades que devem valer para muitos valores gerados automaticamente. A ferramenta procura casos extremos, registra uma seed reproduzível e reduz entradas que falham até chegar a um contraexemplo pequeno.

Property-based testing complementa testes tradicionais. Exemplos continuam úteis para regras específicas, enquanto propriedades exploram combinações que o desenvolvedor talvez não tenha imaginado.

Instalação

npm install -D fast-check

Com Node Test Runner:

import test from 'node:test';
import assert from 'node:assert/strict';
import fc from 'fast-check';

test('reverter duas vezes mantém a string', () => {
  fc.assert(
    fc.property(fc.string(), (value) => {
      const result = [...[...value].reverse()].reverse().join('');
      assert.equal(result, value);
    }),
  );
});

Arbitraries

Arbitraries geram valores e sabem reduzi-los. Exemplos:

fc.string()
fc.integer()
fc.nat()
fc.boolean()
fc.date()
fc.uuid()
fc.emailAddress()
fc.array(fc.integer())
fc.option(fc.string())
fc.record({ id: fc.uuid(), active: fc.boolean() })

Use o arbitrary mais próximo do domínio. Gerar strings genéricas quando o campo é UUID desperdiça execuções em entradas inválidas.

Propriedades de uma ordenação

function ordenar(values) {
  return [...values].sort((a, b) => a - b);
}

test('ordenação preserva valores e ordem', () => {
  fc.assert(
    fc.property(fc.array(fc.integer()), (values) => {
      const result = ordenar(values);

      assert.equal(result.length, values.length);
      assert.deepEqual(
        [...result].sort((a, b) => a - b),
        result,
      );

      const originalCounts = new Map();
      for (const value of values) {
        originalCounts.set(value, (originalCounts.get(value) ?? 0) + 1);
      }
      for (const value of result) {
        const count = originalCounts.get(value) ?? 0;
        assert.ok(count > 0);
        originalCounts.set(value, count - 1);
      }
    }),
  );
});

A propriedade descreve invariantes, não exemplos isolados.

Shrinking

Quando um valor grande falha, fast-check tenta simplificá-lo. Uma lista de centenas de elementos pode virar [0, 0]; uma string complexa pode virar um caractere. Esse processo torna o bug compreensível.

Não substitua arbitraries por geradores aleatórios manuais, pois perderá shrinking e reprodução.

Seed e reprodução

Uma falha exibe seed e caminho. Reproduza:

fc.assert(property, {
  seed: 123456789,
  path: '4:2:0',
});

Depois de corrigir, adicione um teste de exemplo com o contraexemplo mínimo quando ele representa uma regressão importante. Remova seed temporária para restaurar exploração ampla.

Configuração de execuções

fc.assert(property, {
  numRuns: 1000,
  endOnFailure: true,
});

Mais execuções aumentam cobertura probabilística e tempo. Use poucas em PRs e mais em execuções noturnas quando necessário.

Determinismo no CI

Falhas são reproduzíveis pela seed informada. Também é possível configurar seed fixa, mas usar sempre a mesma sequência reduz variedade entre execuções. Uma estratégia é deixar aleatória e registrar seed em toda falha.

Precondições

fc.property(fc.integer(), fc.integer(), (a, b) => {
  fc.pre(b !== 0);
  assert.equal((a / b) * b, a);
});

Precondições descartam casos. Muitas rejeições tornam o teste ineficiente; prefira construir um arbitrary que gere somente valores válidos:

const nonZeroInteger = fc.integer().filter((value) => value !== 0);

Mesmo filter pode rejeitar muito. Combine intervalos ou mapeamentos.

Map e chain

Crie valores derivados:

const positiveMoney = fc
  .integer({ min: 0, max: 1_000_000 })
  .map((cents) => ({ cents, currency: 'BRL' }));

Para dependência entre campos:

const range = fc.integer({ min: 0, max: 100 }).chain((min) =>
  fc.integer({ min, max: 100 }).map((max) => ({ min, max })),
);

Records de domínio

const userArbitrary = fc.record({
  id: fc.uuid(),
  name: fc.string({ minLength: 1, maxLength: 80 }),
  email: fc.emailAddress(),
  roles: fc.uniqueArray(fc.constantFrom('admin', 'editor', 'viewer')),
  active: fc.boolean(),
});

Compartilhe arbitraries próximos ao domínio de testes, sem misturá-los ao código de produção.

Comandos e modelos

Stateful property testing gera sequências de operações e compara sistema real com modelo simplificado. É útil para caches, filas, carrinhos e máquinas de estado.

Um modelo de saldo pode executar depósitos, saques e consultas em ordens variadas. fast-check reduz a sequência até os poucos comandos que provocam inconsistência.

Propriedades úteis

  • round trip: decodificar o que foi codificado retorna o original;
  • idempotência: aplicar operação duas vezes equivale a uma;
  • comutatividade: ordem não altera resultado quando esperado;
  • invariantes: saldo não fica negativo;
  • monotonicidade: adicionar itens não reduz contagem;
  • equivalência: implementação nova coincide com referência;
  • não quebra: parser não lança para entradas permitidas.

Testando parsers

test('JSON serializa e desserializa valores suportados', () => {
  const jsonValue = fc.jsonValue();

  fc.assert(
    fc.property(jsonValue, (value) => {
      assert.deepEqual(JSON.parse(JSON.stringify(value)), value);
    }),
  );
});

Escolha o domínio suportado. JSON não preserva todos os valores JavaScript.

Datas e timezones

Datas geradas encontram limites, anos bissextos e fusos. Não use apenas new Date() atual. Defina intervalo válido para o negócio e normalize timezone explicitamente.

Unicode

Strings aleatórias podem conter caracteres que revelam bugs de comprimento, normalização e divisão por code unit. Use iteradores Unicode ao inverter texto e teste emojis, combinações e caracteres invisíveis quando relevantes.

Segurança

Geradores podem incluir chaves perigosas como __proto__, ajudando a encontrar prototype pollution. Crie propriedades para garantir que merge e parsing não modifiquem protótipos.

Integração com Jest ou Vitest

Use fc.assert dentro do it:

it('normalizar é idempotente', () => {
  fc.assert(
    fc.property(fc.string(), (value) => {
      expect(normalizar(normalizar(value))).toBe(normalizar(value));
    }),
  );
});

Testes assíncronos

test('cache devolve valor gravado', async () => {
  await fc.assert(
    fc.asyncProperty(fc.string(), fc.jsonValue(), async (key, value) => {
      await cache.set(key, value);
      assert.deepEqual(await cache.get(key), value);
    }),
  );
});

Limpe estado entre execuções. Testes assíncronos com dependências reais podem ficar lentos.

Integração e bancos

Property-based tests podem usar Testcontainers, mas o custo cresce. Gere lotes dentro de um único container, use transações de rollback e reduza numRuns. Mantenha propriedades puras sempre que possível.

Tempo limite

Uma entrada pode provocar algoritmo muito lento. Defina limites de tamanho e timeouts. Não esconda uma vulnerabilidade de complexidade apenas reduzindo todos os dados.

Arbitraries grandes

Comece com tamanhos pequenos. fast-check varia tamanho e extremos. Para testes específicos de escala, use benchmark ou carga em vez de tentar cobrir performance apenas com propriedades.

Erros comuns

  • assertar apenas que não lança;
  • reimplementar a função no teste;
  • usar muitos filtros;
  • gerar dados fora do domínio;
  • ignorar seed da falha;
  • usar aleatoriedade manual;
  • tentar substituir todos os exemplos;
  • não limpar estado assíncrono.

CI

Rode propriedades rápidas em todos os PRs. Em agenda noturna, aumente numRuns. Preserve seed e path no log e evite truncar a saída que permite reprodução.

Property testing e mutation testing

fast-check explora entradas; Stryker altera implementação. Juntos, eles aumentam a chance de detectar limites e verificar se propriedades realmente protegem comportamento.

Fluxo recomendado

Escolha uma função com invariantes claros, modele arbitraries do domínio, deixe shrinking trabalhar, reproduza seeds e mantenha exemplos para regressões. Combine com Stryker no Node.js, Node Test Runner, Testcontainers e pipelines em GitHub Actions.

Consulte a documentação oficial sobre property-based testing e a API oficial do fast-check.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita