Pular para o conteúdo
← Voltar para o Skalablog

Artigo publicado

Como aplicar inversão de dependência em testes

Engenharia de Software

Sem inversão de dependência, trocar o Drizzle pelo Prisma obriga você a mexer na camada de regra de negócio. Com ela, a troca acontece na borda da aplicação, na hora de montar as peças. A interface é definida na camada de application, e o repositório concreto passa a depender dela, e não o contrário.

O que é inversão de dependência na prática

A inversão de dependência é o princípio do SOLID que garante que módulos de alto nível, aqueles com a regra de negócio, não dependam de módulos de baixo nível como bancos de dados e APIs. Em vez disso, ambos dependem de abstrações: interfaces definidas na camada de application. A ideia foi popularizada por Robert C. Martin nos anos 2000 e é a base conceitual da arquitetura hexagonal, formulada por Alistair Cockburn em 2005.

Considere um caso de uso createEvent que salva eventos em um banco via Drizzle ORM. Se essa função importa e instancia o repositório diretamente, ela está casada com a implementação. Você não consegue executá-la sem um banco rodando, e trocar o Drizzle pelo Prisma vira um trabalho cirúrgico no coração da aplicação.

A inversão muda a seta da dependência. O caso de uso passa a depender de uma interface EventsRepository, e a classe concreta EventsRepositoryDatabase implementa essa interface. Agora a implementação depende do contrato da sua camada de negócio, e não o contrário.

Por que mock não substitui um bom design

Um erro comum é achar que o Vitest resolve o acoplamento sozinho. Você pode mockar a função createEventsOnDatabase e o teste passa, mas isso não muda o design do código. O mock é uma ferramenta externa: sem ela, a regra de negócio continua impossível de testar isolada.

A premissa da arquitetura hexagonal é testar a application sem os drivers, como a API, e sem os driven adapters, como o banco. Quando o caso de uso importa o repositório concreto, a segunda parte dessa premissa simplesmente não se sustenta. O teste depende de infraestrutura de verdade ou de mágica de biblioteca.

Mock e stub têm valor, mas eles mascaram o problema em vez de eliminá-lo. O que você quer é um design em que o caso de uso nem conheça a existência do banco. Nesse ponto, o stub em memória deixa de ser gambiarra e vira apenas mais uma implementação válida do contrato.

Ports and adapters: a interface como contrato

Na arquitetura hexagonal, a fronteira entre a aplicação e o mundo externo é formada por ports (portas) e adapters (adaptadores). Uma port é uma interface definida na camada de application que descreve o que a regra de negócio precisa. Um adapter é qualquer classe que implementa essa interface, seja com Drizzle, Prisma, MongoDB ou memória.

No exemplo do createEvent, você cria a interface EventsRepository com um método create que recebe o input e retorna uma promise. A camada de application declara apenas a forma do contrato; ela não sabe, nem quer saber, quem executa a gravação.

Esse contrato é o que torna o sistema montável como peças de Lego. O CreateEvent espera qualquer objeto que satisfaça EventsRepository, então EventsRepositoryDrizzle, EventsRepositoryPrisma e InMemoryEventsRepository são intercambiáveis sem tocar uma linha da regra de negócio.

Como aplicar inversão de dependência passo a passo

A transformação cabe em quatro etapas, e a ordem importa. Comece pelo código atual e vá invertendo a dependência aos poucos, com os testes de segurança rodando a cada passo.

  1. Transforme a função em classe. Converta createEvent em uma classe CreateEvents com um método execute(input). Classe aqui não é cerimônia: é o mecanismo que permite receber dependências pelo construtor.

2. Defina a port. Crie a interface EventsRepository na camada de application, com o método create e seus tipos de entrada e saída. Esse contrato pertence à camada de negócio, nunca à infraestrutura.

3. Faça o adapter implementar a interface. Renomeie o repositório concreto para algo como EventsRepositoryDatabase e declare que ele implements EventsRepository. A seta da dependência agora aponta do banco para a application.

4. Injete a dependência. Remova o import e o new do repositório dentro do caso de uso. Receba EventsRepository no construtor, guarde em uma propriedade privada e chame this.eventsRepository.create(input) dentro do execute.

Quem monta a aplicação passa a escolher os adaptadores na borda: new CreateEvents(new EventsRepositoryDrizzle()) na API, ou new CreateEvents(new InMemoryEventsRepository()) no teste. Se a jornada começou em um curso como o Crazystack Typescript, do canal Gustavo Dev Doido, ou no Bootcamp do Dev Doido, esse padrão vai aparecer cedo ou tarde; entender o porquê evita copiar a estrutura no automático. Você encontra material do Gustavo Dev Doido e do Crazystack cobrindo essa base em TypeScript.

Injeção de dependência vs inversão de dependência

Os dois conceitos andam juntos, mas não são a mesma coisa. A inversão de dependência é o princípio de design: a dependência aponta da implementação concreta para a abstração. A injeção de dependência é a técnica de entrega: em vez de a classe criar suas dependências, ela as recebe prontas pelo construtor.

Pense assim: a interface EventsRepository existe por causa da inversão; o parâmetro no construtor de CreateEvents existe por causa da injeção. Um define quem depende de quem, o outro define como a dependência chega ao destino.

Sem injeção, a inversão pela metade: você cria a interface, mas o caso de uso continua instanciando o adaptador concreto internamente, e o teste volta a ficar preso. O par completo é interface definida pela application, adapter implementando a interface e injeção no ponto de montagem.

Testando a regra de negócio sem banco de dados

Com a dependência invertida, o teste da application não precisa de Docker nem de banco. Você escreve um InMemoryEventsRepository que implementa EventsRepository, guarda os eventos em uma lista simples e devolve o input no create. É um stub de meia dúzia de linhas.

Aí vem o teste decisivo: pare a API, execute docker stop e rode npx vitest apenas na suíte da application. Se tudo passa, você provou que a regra de negócio não depende de recurso externo, que é exatamente o objetivo da arquitetura hexagonal.

O mesmo contrato dá flexibilidade em produção. Se amanhã o repositório Drizzle der lugar a uma versão com Prisma ou a um repositório em memória V2, você troca uma linha na composição da API. Desde que a nova classe implemente EventsRepository, a aplicação continua funcionando e os testes da application seguem verdes.

FAQ

  • Inversão de dependência é a mesma coisa que injeção de dependência? Não. A inversão é o princípio que faz a implementação depender da abstração definida pela regra de negócio. A injeção é a técnica de passar a dependência pronta para a classe, normalmente pelo construtor.
  • Preciso de arquitetura hexagonal para usar inversão de dependência? Não. O princípio funciona em qualquer estrutura, mas a arquitetura hexagonal formaliza a ideia com ports e adapters e deixa o objetivo explícito: testar a regra de negócio isolada de drivers e recursos.
  • Mock ainda é útil quando aplico inversão de dependência? Sim, mas ele muda de papel. Com a interface no lugar, você raramente precisa do mock do framework; um stub em memória que implementa o contrato costuma ser mais simples, mais rápido e mais legível.
  • Módulo de alto nível e de baixo nível significam o quê? Alto nível é o módulo que carrega a regra de negócio, como a camada de application. Baixo nível é o que serve a regra sem fazer parte dela, como o repositório de banco ou o cliente de API externa.

Do vídeo para o artigo escrito

A lição central aqui é que testabilidade vem de design, não de ferramenta: quando a regra de negócio depende de um contrato, o banco vira um detalhe trocável. Aulas como essa, dadas em mentoria ao vivo, costumam ficar presas em vídeo, com o melhor conteúdo difícil de consultar depois.

Se você tem conhecimento assim guardado em vídeos do YouTube, o Skala Blog transforma essa matéria-prima em artigo: você cola a URL do vídeo, a transcrição é gerada e o texto estruturado sai pronto para revisar e publicar.

Source video