← volver al blog

El bug de coverage 0% en CI que me costó 3 horas: Vitest y los patrones de exclusión

Un pattern de exclude mal configurado en vitest.config.ts que hacía que el coverage reportara 0% solo en CI.

El síntoma

Local: coverage 87%. CI: coverage 0%. Mismo commit, misma versión de Node.

❯ vitest run --coverage
 % Coverage report from v8
------|---------|----------|---------|---------|
File  | % Stmts | % Branch | % Funcs | % Lines |
------|---------|----------|---------|---------|
All   |       0 |        0 |       0 |       0 |
------|---------|----------|---------|---------|

La investigación

Después de descartar versiones de Node, cachés y permisos, encontré el culpable en vitest.config.ts:

export default defineConfig({
  test: {
    coverage: {
      exclude: [
        'node_modules/**',
        'dist/**',
        '**/*.test.ts',
        'src/generated/**',
      ],
    },
  },
});

¿Se ve bien, no? El problema era que en CI los archivos se compilaban en un paso previo y el directorio dist contenía los source maps que Vitest usaba para resolver los paths.

El fix

export default defineConfig({
  test: {
    coverage: {
      include: ['src/**/*.ts'],  // ser explícito con lo que SÍ queremos
      exclude: [
        '**/*.test.ts',
        '**/*.spec.ts',
        'src/generated/**',
      ],
    },
  },
});

La lección: prefiere include sobre exclude cuando defines coverage. Es más predecible y no depende de la estructura de directorios del entorno.