Você já passou a noite em claro porque o Singleton impedia escrever testes?
A lógica está correta, mas, ao adicionar testes, o código continua acessando o DB real e chamando a API real.
Vou começar pela conclusão.
Como o Singleton busca o objeto diretamente no código, não é possível substituí-lo por um objeto falso durante os testes.
Já a injeção de dependência recebe os objetos necessários de fora, permitindo trocá-los livremente nos testes.
Hoje vamos entender a diferença entre os dois usando código real.
Por que o Singleton bloqueia os testes?
Singleton é um objeto que existe apenas uma vez em todo o programa.
É prático: basta uma linha, Database.shared, para acessá-lo de qualquer lugar.
É aí que o problema começa.
Quando o objeto é buscado diretamente dentro de uma função, ela fica fortemente acoplada ao banco de dados real.
Durante os testes, não há como dizer: “Use um DB falso em vez do real”.
// Buscar o Singleton diretamente dentro da função
func saveOrder(_ order: Order) {
// Fica fortemente acoplado ao DBreal
let db = Database.shared
db.insert(order) // Não há como impedir isso nos testes
}
Para testar esse código, o DB real precisa estar ligado.
Se o DB estiver desligado, o teste também falhará.
A lógica não está errada, mas a infraestrutura faz o teste ficar vermelho.
Como fazer injeção de dependência?
A injeção de dependência é menos complicada do que parece.
Em vez de buscar o objeto dentro da função, crie-o fora e passe-o como argumento.
Literalmente, você “injeta” o que é necessário.
// DBReceber de fora como parâmetro
func saveOrder(_ order: Order, db: Database) {
db.insert(order) // Quem chama decide qual DBé
}
A diferença é de apenas uma linha, certo?
Basta trocar o acesso direto por Database.shared para receber o objeto como parâmetro.
Ainda assim, essa pequena mudança transforma completamente os testes.
No código real, você passa o DB real; nos testes, um DB falso.
Também é comum recebê-lo pelo construtor.
class OrderService {
private let db: Database
// Receber e guardar o objeto de fora ao criá-lo
init(db: Database) { self.db = db }
}
Depois de recebê-lo, você pode usá-lo em qualquer lugar da classe como self.db, de forma muito mais limpa.
Substituindo por um objeto falso nos testes
Agora chegamos à parte realmente interessante.
Como a dependência vem de fora, nos testes basta passar um DB falso no lugar do real (normalmente chamado de mock).
@Test func Ao salvar_o pedido, ele é_DBentregue a_() {
let fake = FakeDatabase() // Falso, não real
saveOrder(Order(), db: fake)
#expect(fake.savedCount == 1) // Verificar apenas se o salvamento foi chamado
}
Não é preciso ligar o DB real.
Você também não precisa de rede nem de servidor externo. Tudo termina instantaneamente na memória.
Com isso, os testes ficam mais rápidos e, principalmente, estáveis.
Assim, é possível validar apenas a lógica, sem depender das condições externas.
Singleton vs. injeção de dependência: uma comparação rápida
Resumi a diferença entre os dois nesta tabela.
| Categoria | Uso direto de Singleton | Injeção de dependência |
|---|---|---|
| Onde o objeto é obtido | Buscado diretamente dentro da função | Passado de fora |
| Troca do objeto falso | Praticamente impossível | Pode ser feita livremente |
| Velocidade dos testes | Lenta (exige recursos reais) | Rápida (processada na memória) |
| Estabilidade dos testes | Oscila conforme as condições externas | Permite validar apenas a lógica |
| Acoplamento | Alto | Baixo |
O Singleton, por si só, não é necessariamente ruim.
Ele é conveniente quando realmente só pode existir uma instância, como no caso de configurações.
O problema é o hábito de “buscar diretamente de qualquer lugar”. Isso amarra os testes.
Perguntas frequentes (Q&A)
P. Para fazer injeção de dependência, preciso obrigatoriamente de uma biblioteca de DI, como Swinject?
Não. Passar dependências por parâmetros ou pelo construtor, como no código de hoje, já é injeção de dependência. A biblioteca apenas gerencia isso automaticamente.
P. Não fica confuso quando há parâmetros demais?
Fica. Por isso, normalmente usamos injeção pelo construtor para receber tudo de uma vez ou agrupamos itens relacionados. Se os parâmetros continuam aumentando, pode ser um sinal de que a classe está fazendo coisas demais.
Se os testes continuam travados por recursos reais, mude apenas um ponto seguindo a abordagem de hoje.
Altere o código que busca objetos diretamente para recebê-los de fora.
Esse pequeno hábito torna os testes muito mais agradáveis de escrever. Bons códigos!

