Pular para o conteúdo
← escritos

Como desbloqueei testes que travavam no SQLite :memory: com AdonisJS

PT-BR

O problema

Estava implementando uma feature nova no backend (import de base de conhecimento do Freshdesk) e os testes funcionais passaram a falhar no setup — antes mesmo de rodar qualquer teste. O seed tentava inserir registros e recebia table tickets has no column named type. Parecia que a migration que adicionava a coluna não estava executando. Mas todas as migrations anteriores funcionavam. O que mudou?

A resposta esteve escondida no MigrationRunner do AdonisJS Lucid.

O que descobri sobre o MigrationRunner

Ao ler o código fonte do @adonisjs/lucid, entendi que MigrationRunner.run() captura erros silenciosamente:

async run() {
  try {
    await this.runUp();
  } catch (error) {
    this.error = error;  // erro registrado, mas nunca re-lançado
  }
  await this.shutdown();
}

Se uma migration falha — por timeout, erro de SQL ou qualquer outro motivo — o run() termina normalmente e o bootstrap segue para o seed. O seed falha, o setup retorna erro, e a causa real nunca aparece nos logs. Isso significa que qualquer migration que não execute corretamente é engolida pelo framework.

Por que cada migration criava uma transaction no SQLite :memory:

O MigrationRunner roda cada migration dentro de uma transaction por padrão:

const client = await this.client.transaction();

Com pool: { min: 0, max: 1 } no SQLite :memory:, o pool tem uma única conexão. Cada transaction precisa adquirir essa conexão. Se a anterior não liberou a tempo (ou o better-sqlite3 tem comportamento síncrono que conflita com o pooling assíncrono do tarn.js), a próxima transaction dá timeout.

O resultado: todas as migrations que dependiam de transaction ficavam presas. E o seed — que usa a mesma conexão do pool — não enxergava as colunas adicionadas pelas migrations bloqueadas.

A solução

Duas mudanças resolveram o problema:

1. Desabilitar transactions nas migrations de teste

No config/database.ts, a conexão memory passou a incluir disableTransactions: true:

memory: {
  client: 'better-sqlite3',
  connection: { filename: ':memory:' },
  useNullAsDefault: true,
  migrations: {
    naturalSort: true,
    paths: ['database/migrations'],
    disableTransactions: true,  // ← impede timeout de pool
  },
},

Cada migration agora usa this.client diretamente em vez de criar uma transaction separada. Com isso, as migrations completam sem adquirir múltiplas conexões do pool.

2. Verificar o erro do migrator no bootstrap

No tests/bootstrap.ts, adicionei uma verificação após migrator.run():

await migrator.run();
if (migrator.error) {
  throw migrator.error;  // propaga o erro em vez de ser silenciado
}

Se qualquer migration falhar agora, o teste falha com o erro real — não com um column does not exist que mascara a causa.

O que aprendi

  1. Sempre verificar o status do migrator depois de run(). Não confie que o framework propaga erros silenciosamente — ele pode não fazer.

  2. disableTransactions existe por um motivo. Em bancos de teste em memória, transactions não são apenas desnecessárias — elas podem causar timeouts de pool. Para SQLite :memory:, desabilitar transactions elimina o gargalo.

  3. O teste errado te leva longe da causa real. O erro table has no column named type sugeriu que o @column no model estava faltando. Mas a coluna já existia no model. O problema era na infraestrutura de teste, não no código. Quando um teste falha, pergunte: "estou testando o código certo, ou estou testando o ambiente?"

  4. Leia o código do framework quando algo parece impossível. O MigrationRunner.run() é 50 linhas. Entender que ele engole erros transformou 4 horas de frustração em 20 minutos de diagnóstico.

Essa experiência reforça uma verdade que todo dev backend deveria internalizar: testes de integração não são confiáveis quando a infraestrutura de teste é opaca. Se o framework esconde falhas, você precisa de camadas de verificação próprias. Não é opcional — é infraestrutura.

  • Testing
  • AdonisJS
  • SQLite
  • Backend
  • NodeJS

Comments

No comments yet. Be the first!