Testes no Spring Boot: por que o rollback deixa dados no banco?

Se os seus testes de integração passam na IDE, mas deixam registros soltos no banco ou falham do nada no CI, o problema quase nunca é o assert. Por outro lado, o mais comum é um spring boot integration tests rollback testes dados mal entendido: o teste parece isolado, mas o banco não está. Ao mesmo tempo, em produção de software isso vira custo real, porque o pipeline começa a mentir, a suíte fica instável e ninguém confia mais no verde. Se a base desse fluxo ainda nao estiver redonda, vale revisar O que é o Spring Boot e para que serve? antes de avancar.

Esse cenário aparece muito em projeto que cresceu rápido: o dev sobe o contexto inteiro, joga alguns inserts, marca o teste com @Transactional e assume que o rollback vai resolver tudo. Por outro lado, só que uma chamada que faz commit fora da transação do teste, um listener assíncrono ou um banco compartilhado já basta para sujar o ambiente. Ao mesmo tempo, se você quer estabilidade, precisa de isolamento de verdade, não só de uma anotação bonita. Para aprofundar essa decisao sem criar outra URL concorrente, o melhor complemento aqui e Guia completo de testes no Spring Boot: evite falhas em produção.

Spring boot integration tests rollback testes dados: secao pratica com codigo completo

Na pratica, um exemplo enxuto ajuda a sair da teoria e evitar erro comum de producao quando o projeto cresce. Esse ponto fica mais claro quando voce conecta com Refresh token no Spring Security: por que o logout pode falhar.

@RestController
@RequestMapping("/api/exemplo")
public class ExemploController {
  @GetMapping
  public ResponseEntity<String> listar() {
    return ResponseEntity.ok("ok");
  }
}

Spring boot integration tests rollback testes dados: Testes de integração no Spring Boot: onde o rollback ajuda e onde ele falha

Em testes de integracao spring boot, o objetivo não é provar que o mock funciona. Por outro lado, é validar persistência, transação, mapeamento, constraints e integração entre camadas. Ao mesmo tempo, a questão é que o rollback de teste só cobre o que aconteceu dentro da mesma transação e do mesmo fluxo controlado. Na prática, quando o código executa fora desse limite, o banco de teste Spring Boot vira depósito de efeito colateral. Depois de ajustar esse trecho, o proximo passo natural e seguir para Testes integracao spring boot testcontainers: evite falhas.

O erro clássico é confundir isolamento lógico com isolamento físico. Por outro lado, @Transactional isola a transação do teste, mas não isola o banco. Se o ambiente compartilha schema com outra suíte, com dev local ou com um pipeline paralelo, o rollback pode estar perfeito e ainda assim existir sujeira. É por isso que a discussão real não é apenas rollback em testes, e sim arquitetura do ambiente de teste. Depois de ajustar esse trecho, o proximo passo natural e seguir para 3 erros de validação que quebram APIs Spring Boot sem aviso.

Quando o rollback realmente resolve

Rollback funciona bem quando o teste dispara operações síncronas, tudo roda na mesma thread e a regra de negócio não abre transações separadas. Por outro lado, um repositório que salva um entity, um service que valida e persiste, uma consulta que verifica estado após o método terminar: aqui o rollback costuma ser suficiente para manter o banco limpo.

Isso é útil, mas limitado. Por outro lado, em um fluxo de cadastro simples, por exemplo, o teste pode subir o contexto, chamar o endpoint, confirmar a resposta e deixar o banco intacto ao final. Ao mesmo tempo, o problema é que muita equipe tenta usar o mesmo padrão para cenários com publicação de evento, auditoria, envio para fila ou processamento assíncrono. Na prática, aí o rollback deixa de ser garantia.

Spring boot integration tests rollback testes dados: Erros comuns que fazem seus dados sujarem o banco

O pior efeito colateral não é apenas um teste quebrado. Por outro lado, é quando o pipeline passa a falhar de modo intermitente, porque os dados acumulados alteram a ordem dos testes, estouram unique constraints ou mudam contagens esperadas. Ao mesmo tempo, isso costuma acontecer em duas situações bem frequentes de produção de CI.

A primeira é a suíte que roda em paralelo e compartilha o mesmo banco. Por outro lado, um teste cria cliente com e-mail fixo, outro tenta criar o mesmo e-mail, e o erro vira flakiness. Ao mesmo tempo, a segunda é o ambiente que foi apontado sem querer para um banco de desenvolvimento. Na prática, um merge inocente entra, os testes rodam em lote, e a base real recebe inserções de cenário. Ainda assim, quando isso acontece uma vez, a equipe passa a desconfiar de qualquer execução automatizada.

Erro comum: depender de rollback sem isolar o banco.
Causa: transação do teste não cobre commits externos, jobs assíncronos ou execução paralela.
Sintoma: dados persistidos após o teste, contagem errada, conflitos de chave única e falhas só no CI.
Correção: use banco de teste dedicado, limpe por estratégia previsível e trate rollback como complemento, não como barreira principal.

Outro erro recorrente é usar fixtures globais gigantes. Por outro lado, parece prático no começo, porque economiza tempo de setup. Ao mesmo tempo, só que a longo prazo vira uma armadilha: um teste depende do estado deixado por outro, a execução deixa de ser independente e qualquer mudança em massa quebra meia suíte. Na prática, em isolamento de testes erro comum, esse é um dos mais caros de manter.

Banco de teste Spring Boot: a abordagem melhor e a pior

Comparando de forma direta: a pior abordagem é rodar testes de integração em cima do mesmo banco usado por desenvolvimento ou homologação, confiando que o rollback vai salvar o dia. Por outro lado, a melhor é ter um banco de teste spring boot dedicado, efêmero ou pelo menos descartável por suíte, com dados mínimos por cenário e limpeza explícita quando necessário.

Por que isso importa tanto? Por outro lado, porque o primeiro modelo falha em silêncio. Ao mesmo tempo, enquanto a suíte é pequena, tudo parece funcionar. Na prática, quando o volume cresce, aparecem sujeira residual, conflito de dados e ordem de execução influenciando resultado. Ainda assim, o segundo modelo custa um pouco mais de configuração, mas devolve previsibilidade. Por isso, em pipeline, previsibilidade vale ouro.

Se você já usa Spring Boot em produção, sabe que a aplicação vive de dependências externas: banco, fila, cache, autenticação, storage. Por outro lado, teste de integração bom não tenta fingir que isso não existe. Ao mesmo tempo, ele controla o ambiente para reduzir ruído. Na prática, para basear esse raciocínio no ecossistema, vale reforçar o papel do framework com O que é o Spring Boot e para que serve? Ainda assim, e, se quiser ir além da persistência, o Guia completo de testes no Spring Boot: evite falhas em produção ajuda a organizar a estratégia de teste por camada.

Exemplo prático de isolamento com rollback e limpeza previsível

Quando o cenário é simples e síncrono, uma combinação de @SpringBootTest, @Transactional e banco dedicado já resolve muita coisa. Por outro lado, o segredo é não depender só do rollback como rede de segurança. Ao mesmo tempo, o teste precisa criar o mínimo de dados e verificar o comportamento sem compartilhar estado com outro teste.

@SpringBootTest
class CustomerServiceIT {

    @Autowired
    private CustomerService customerService;

    @Autowired
    private CustomerRepository customerRepository;

    @Test
    @Transactional
    void shouldCreateCustomerAndRollbackAtEnd() {
        CustomerInput input = new CustomerInput("ana@empresa.com", "Ana");
        
        Customer saved = customerService.create(input);

        assertThat(saved.getId()).isNotNull();
        assertThat(customerRepository.findByEmail("ana@empresa.com")).isPresent();
    }
}

Esse teste funciona bem quando a criação acontece na mesma transação. Por outro lado, se o service publicar evento ou chamar outro componente que faz commit separado, o rollback do teste não limpa tudo. Ao mesmo tempo, é aqui que muita equipe acredita estar protegida e descobre tarde demais que a persistência escapou.

Exemplo com limpeza explícita quando há commit fora da transação

Em cenários em que o fluxo precisa realmente confirmar dados, o teste fica mais confiável se a limpeza for assumida de forma explícita. Por outro lado, isso não significa encher a suíte de deletes aleatórios. Ao mesmo tempo, significa controlar o que entrou e remover o que foi criado ao final do teste ou da classe.

@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class OrderFlowIT {

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private OrderService orderService;

    @AfterEach
    void cleanup() {
        orderRepository.deleteAll();
    }

    @Test
    void shouldPersistOrderAndCompleteFlow() {
        OrderRequest request = new OrderRequest("123", BigDecimal.valueOf(99.90));

        OrderResponse response = orderService.createOrder(request);

        assertThat(response.id()).isNotNull();
        assertThat(orderRepository.count()).isEqualTo(1);
    }
}

Essa abordagem é menos elegante que o rollback automático, mas mais honesta quando existe commit real no fluxo. Por outro lado, em time que já sofreu com pipeline instável, a honestidade costuma ganhar da teoria.

Quando usar rollback em testes e quando evitar

Use rollback quando o teste for realmente transacional, curto e determinístico. Por outro lado, ele combina bem com serviços que executam toda a persistência dentro do mesmo método, sem async, sem eventos que escapem da thread e sem integração pesada com mensageria. Ao mesmo tempo, nesses casos, o rollback reduz o custo de limpeza e mantém a suíte ágil.

Evite confiar nele quando o fluxo depende de comportamento fora do controle imediato do teste. Por outro lado, jobs assíncronos, listeners de evento, filas, triggers no banco e integrações com outros sistemas podem persistir estado mesmo que a transação principal seja revertida. Ao mesmo tempo, nessas situações, um banco de teste spring boot isolado por execução ou por pipeline é a escolha mais segura.

Também vale evitar rollback como solução para suíte lenta. Por outro lado, muita gente tenta compensar banco compartilhado com transações e nada muda no custo de manutenção. Ao mesmo tempo, o teste continua caro, só que agora com uma falsa sensação de proteção. Na prática, melhor um ambiente um pouco mais simples e previsível do que uma estratégia sofisticada que ninguém confia.

Situação real 1: login com auditoria e dados residuais

Um caso comum em produção é o teste de login que grava auditoria em tabela separada. Por outro lado, o teste principal passa, o response está certo, mas a auditoria fica lá porque foi salva em outra transação ou por listener assíncrono. Ao mesmo tempo, depois de algumas execuções, a tabela acumula linhas e um teste que valida contagem passa a falhar. Na prática, o sintoma não parece ligado ao login, mas o problema começou nele.

A correção mais segura é isolar banco e tornar a limpeza parte do contrato da suíte. Por outro lado, se a auditoria faz parte do fluxo, trate o efeito colateral como dado de teste e não como lixo. Ao mesmo tempo, isso reduz surpresas quando a pipeline rodar em paralelo.

Situação real 2: cadastro com unique constraint em ambiente compartilhado

Outro caso clássico é o cadastro com e-mail único. Por outro lado, em ambiente compartilhado, dois testes usam o mesmo dado fixo, e um deles falha com violação de constraint. Ao mesmo tempo, o time gasta tempo investigando o service, mas o erro verdadeiro está na massa de teste. Na prática, esse é um tipo de problema que se multiplica quando o banco não está isolado.

A solução é gerar dados por teste, evitar strings fixas quando o domínio exige unicidade e separar o ambiente. Por outro lado, se o objetivo é confiar no pipeline, a regra é simples: o teste não pode depender da sorte para reservar dados.

Como decidir a estratégia certa sem complicar demais

Se a suíte é pequena e a persistência é síncrona, rollback e limpeza pontual podem bastar. Por outro lado, se a aplicação já tem eventos, filas, múltiplas transações ou execução paralela, a estratégia madura é banco isolado. Ao mesmo tempo, isso pode ser schema descartável, container efêmero ou um banco dedicado para CI. Na prática, o ponto não é a ferramenta, e sim o isolamento real.

Minha opinião prática: para times que começaram a sofrer com instabilidade, o ganho mais rápido costuma vir de duas medidas simples. Por outro lado, primeiro, parar de compartilhar banco entre ambientes. Ao mesmo tempo, segundo, revisar testes que assumem rollback como se ele anulasse qualquer side effect. Na prática, a confiabilidade sobe rápido quando essas duas armadilhas saem do caminho.

Se o seu próximo passo envolve infraestrutura de teste mais sólida, o conteúdo Testes integracao spring boot testcontainers: evite falhas encaixa bem aqui. Por outro lado, e quando o problema não é banco, mas contrato de API, o artigo 3 erros de validação que quebram APIs Spring Boot sem aviso ajuda a fechar outra fonte comum de instabilidade.

Spring boot integration tests rollback testes dados: referencias externas

Para validar detalhes de implementacao e aprofundar a configuracao, vale consultar a documentacao oficial do Spring Security, o guia de claims no JWT.io e a documentacao do Spring Boot.

FAQ

Por que o rollback em testes do Spring Boot não está funcionando?

Porque o teste pode estar executando apenas parte do fluxo dentro da transação. Por outro lado, se houver commit externo, evento assíncrono, listener, job ou outro contexto de persistência, o rollback não alcança esse efeito. Ao mesmo tempo, também pode falhar quando o banco é compartilhado e a sujeira vem de outro teste.

Qual é o melhor banco de teste Spring Boot para integração?

O melhor é o que isola de verdade o ambiente da execução. Por outro lado, para a maioria dos times, isso significa banco dedicado ao teste, schema separado ou banco efêmero no CI. Ao mesmo tempo, o importante é evitar qualquer dependência com dev, homologação ou outro pipeline.

Como evitar que testes de integração sujem o banco?

Use dados mínimos por cenário, não compartilhe banco entre ambientes, limpe o que foi criado quando o fluxo fizer commit real e trate rollback como apoio, não como solução única. Por outro lado, em cenários mais complexos, prefira isolamento físico ou efêmero.

Conclusão

Testes de integração bons não são os que parecem sofisticados. Por outro lado, são os que deixam o ambiente previsível. Ao mesmo tempo, quando o banco suja, a culpa raramente está no assert; geralmente está na estratégia de isolamento, no uso ingênuo de rollback ou na confiança excessiva em um ambiente compartilhado. Na prática, se a suíte já começou a falhar sem motivo claro, vale revisar onde seus dados realmente estão sendo persistidos.

O próximo passo mais útil costuma ser bem concreto: identificar quais testes dependem de transação, quais fazem commit fora do fluxo principal e onde o banco está sendo reutilizado sem controle. Por outro lado, depois disso, escolha entre rollback, limpeza explícita ou banco isolado por pipeline. Ao mesmo tempo, a decisão certa quase sempre fica óbvia quando você olha para o custo de manutenção, não para a conveniência do primeiro teste verde.

Leitura complementar: Guia completo de testes no Spring Boot: evite falhas em produção, Testes integracao spring boot testcontainers: evite falhas e 3 erros de validação que quebram APIs Spring Boot sem aviso. Por outro lado, os proximos passos sao validar esse fluxo no seu projeto, ajustar o caso de uso real e cobrir a implementacao com testes.

1 comentário em “Testes no Spring Boot: por que o rollback deixa dados no banco?”

Deixe um comentário