Skip to content

Testing

Convenções de teste e exigência de 100% de coverage.


Runner

ItemValor
Frameworkvitest 2.1.x
Comandonpm run test (vitest run)
Coveragenpx vitest run --coverage (provider v8)
Configvitest.config.ts
Setup globalvitest.setup.ts (silencia console.*)

Convenções

RegraDetalhe
Sufixo.spec.ts (nunca .test.ts)
Localizaçãoco-locado com o arquivo testado
Idiomadescribe e it em inglês
vi.mockSempre no topo do arquivo, antes dos imports do código testado
Builders inlineHelpers (buildPayload, buildIncoming, buildEvent) ficam no topo do spec sem pasta fixtures/ separada
Sem comentáriosVale a regra geral do code-style
ImportsSempre via path alias (@common/, @adapters/, etc)
Sem strings em pt-BR para assertions de logProduction logs usam pt-BR; assertions verificam estrutura, não cópia exata

Estrutura de Cobertura

vitest.config.ts declara:

typescript
coverage: {
  provider: 'v8',
  include: [
    'src/**/*.service.ts',
    'src/**/*.mapper.ts',
    'src/**/*.client.ts',
    'src/**/*.handler.ts',
    'src/helpers/**/*.ts',
    'src/common/**/*.ts',
    'src/handler.ts'
  ],
  exclude: ['src/**/*.spec.ts'],
  thresholds: { statements: 100, branches: 100, functions: 100, lines: 100 }
}

Excluídos de coverage (intencional):

  • src/**/*.interface.ts: apenas tipos
  • src/**/*.types.ts: apenas tipos
  • src/**/*.enum.ts: apenas enums
  • src/domain/constants/**: exceto bot-hours.const.ts que tem parseHour

A barra é 100% em todas as métricas. CI falha se algum branch ficar descoberto.


Setup Global de Console

vitest.setup.ts:

typescript
import { beforeAll, vi } from 'vitest'

beforeAll(() => {
  vi.spyOn(console, 'log').mockImplementation(() => undefined)
  vi.spyOn(console, 'warn').mockImplementation(() => undefined)
  vi.spyOn(console, 'error').mockImplementation(() => undefined)
})

Razão: o Logger usa console.* direto. Sem o silenciamento, cada spec polui a saída com JSON estruturado.

O logger.spec.ts lê as chamadas via vi.mocked(console.log).mock.calls para verificar o formato.


Padrões por Tipo de Spec

Unit de Service

typescript
describe('AutoReplyService', () => {
  const zapi = { sendText: vi.fn() }
  let service: AutoReplyService

  beforeEach(() => {
    zapi.sendText.mockReset().mockResolvedValue({ zaapId: 'z', messageId: 'm' })
    service = new AutoReplyService(zapi as unknown as ZapiClient)
  })

  it('calls sendText with the incoming phone and OFF_HOURS_MESSAGE', async () => {
    await service.replyOffHours(buildIncoming())
    expect(zapi.sendText).toHaveBeenCalledWith({ phone: '...', message: OFF_HOURS_MESSAGE })
  })
})

Unit de Adapter (com vi.mock)

typescript
vi.mock('axios')

const mockedAxios = axios as unknown as { create: Mock }

const setupHttp = () => {
  const post = vi.fn()
  mockedAxios.create = vi.fn(() => ({ post }))
  return post
}

Integração End-to-End

src/handler.spec.ts mocka apenas axios e @helpers/business-hours, importa @/handler dinamicamente com vi.resetModules() para garantir bootstrap limpo a cada teste, e exercita o pipeline completo:

typescript
const loadHandler = async () => {
  vi.resetModules()
  const post = vi.fn().mockResolvedValue({ data: { zaapId: 'z', messageId: 'm' } })
  mockedAxios.create = vi.fn(() => ({ post }))
  const mod = await import('@/handler')
  return { handler: mod.handler, post }
}

Cobertura por Arquivo (estado atual)

ArquivoSpecs
src/handler.ts7 (rotas + integração + 404 + 502)
src/handlers/inbound-webhook.handler.ts5
src/services/auto-reply.service.ts2
src/services/bot-router.service.ts2
src/adapters/zapi/zapi.client.ts5
src/adapters/zapi/zapi.mapper.ts11
src/helpers/business-hours.ts6
src/helpers/phone-normalize.ts4
src/common/logger.ts5
src/common/error-boundary.ts5
Total52 specs, 100% coverage

Validação Antes de Finalizar

bash
npm run test              # vitest run (todas as 52 specs)
npx vitest run --coverage # garantir 100% de coverage
npx tsc --noEmit          # type-check
npm run format            # prettier (se configurado)
npm run lint              # eslint (se configurado)

Coverage abaixo de 100% falha o CI pelo thresholds configurado.


Documento atualizado em Maio 2026