Appearance
Testing
Convenções de teste e exigência de 100% de coverage.
Runner
| Item | Valor |
|---|---|
| Framework | vitest 2.1.x |
| Comando | npm run test (vitest run) |
| Coverage | npx vitest run --coverage (provider v8) |
| Config | vitest.config.ts |
| Setup global | vitest.setup.ts (silencia console.*) |
Convenções
| Regra | Detalhe |
|---|---|
| Sufixo | .spec.ts (nunca .test.ts) |
| Localização | co-locado com o arquivo testado |
| Idioma | describe e it em inglês |
vi.mock | Sempre no topo do arquivo, antes dos imports do código testado |
| Builders inline | Helpers (buildPayload, buildIncoming, buildEvent) ficam no topo do spec sem pasta fixtures/ separada |
| Sem comentários | Vale a regra geral do code-style |
| Imports | Sempre via path alias (@common/, @adapters/, etc) |
| Sem strings em pt-BR para assertions de log | Production 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 tipossrc/**/*.types.ts: apenas tipossrc/**/*.enum.ts: apenas enumssrc/domain/constants/**: excetobot-hours.const.tsque temparseHour
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)
| Arquivo | Specs |
|---|---|
src/handler.ts | 7 (rotas + integração + 404 + 502) |
src/handlers/inbound-webhook.handler.ts | 5 |
src/services/auto-reply.service.ts | 2 |
src/services/bot-router.service.ts | 2 |
src/adapters/zapi/zapi.client.ts | 5 |
src/adapters/zapi/zapi.mapper.ts | 11 |
src/helpers/business-hours.ts | 6 |
src/helpers/phone-normalize.ts | 4 |
src/common/logger.ts | 5 |
src/common/error-boundary.ts | 5 |
| Total | 52 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