Transações programáticas no Spring TestContext Framework

Transações programáticas no Spring TestContext Framework

1. Introdução

Spring tem excelente suporte para gerenciamento de transações declarativas emapplication code, bem como emintegration tests.

No entanto, podemos ocasionalmente precisar de um controle refinado sobre os limites da transação.

Neste artigo, veremoshow to programmatically interact with automatic transactions set up by Spring in transactional tests.

2. Pré-requisitos

Vamos supor que temos alguns testes de integração em nosso aplicativo Spring.

Especificamente, estamos considerando testes que interagem com um banco de dados, por exemplo, para verificar se nossa camada de persistência está se comportando corretamente.

Vamos considerar uma classe de teste padrão - anotada como transacional:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { HibernateConf.class })
@Transactional
public class HibernateBootstrapIntegrationTest { ... }

Nesse teste,every test method is wrapped in a transaction, which gets rolled back when the method exits.

É claro que também é possível anotar apenas métodos específicos. Tudo o que discutiremos neste artigo se aplica a esse cenário também.

3. A classeTestTransaction

Passaremos o resto do artigo discutindo uma única aula:org.springframework.test.context.transaction.TestTransaction.

Esta é uma classe de utilitário com alguns métodos estáticos que podemos usar para interagir com transações em nossos testes.

Cada método interage com a única transação atual existente durante a execução de um método de teste.

3.1. Verificando o estado da transação atual

Uma coisa que costumamos fazer nos testes é verificar se as coisas estão no estado em que deveriam estar.

Portanto, podemos verificar se há uma transação ativa no momento:

assertTrue(TestTransaction.isActive());

Ou, podemos estar interessados ​​em verificar se a transação atual está sinalizada para reversão ou não:

assertTrue(TestTransaction.isFlaggedForRollback());

Se for, o Spring o reverterá imediatamente antes de terminar, automática ou programaticamente. Caso contrário, ele irá confirmá-lo antes de fechá-lo.

3.2. Sinalizando uma transação para confirmação ou reversão

Podemos alterar programaticamente a política para confirmar ou reverter a transação antes de fechá-la:

TestTransaction.flagForCommit();
TestTransaction.flagForRollback();

Normalmente, as transações nos testes são sinalizadas para reversão quando iniciadas. No entanto, se o método tiver uma anotação@Commit, eles começam sinalizados para confirmação:

@Test
@Commit
public void testFlagForCommit() {
    assertFalse(TestTransaction.isFlaggedForRollback());
}

Observe que esses métodos apenas sinalizam a transação, como seus nomes sugerem. Ou seja,the transaction isn’t committed or rolled back immediately, but only just before it ends.

3.3. Iniciando e finalizando uma transação

Para confirmar ou reverter uma transação, deixamos o método sair ou terminamos explicitamente:

TestTransaction.end();

Se, posteriormente, quisermos interagir com o banco de dados novamente, precisamos iniciar uma nova transação:

TestTransaction.start();

Observe que a nova transação será sinalizada para rollback (ou confirmação) de acordo com o padrão do método. Em outras palavras, chamadas anteriores paraflagFor… não têm nenhum efeito nas novas transações.

4. Alguns detalhes de implementação

TestTransaction não é nada mágico. Agora veremos sua implementação para aprender um pouco mais sobre transações em testes com Spring.

Podemos ver que seus poucos métodos simplesmente acessam a transação atual e encapsulam parte de sua funcionalidade.

4.1. De ondeTestTransaction obtém a transação atual?

Vamos direto ao código:

TransactionContext transactionContext
  = TransactionContextHolder.getCurrentTransactionContext();

TransactionContextHolder é apenas um invólucro estático em torno de umThreadLocal contendo umTransactionContext.

4.2. Quem define o contexto local do segmento?

Se olharmos para quem chama o métodosetCurrentTransactionContext, descobriremos que há apenas um chamador:TransactionalTestExecutionListener.beforeTestMethod.

TransactionalTestExecutionListener é o ouvinte que o Springs configura automaticamente nos testes anotados em@Transactional.

Observe queTransactionContext não contém uma referência a nenhuma transação real; em vez disso, é apenas uma fachada sobrePlatformTransactionManager.

Sim, esse código é altamente estratificado e abstrato. Tais são, freqüentemente, as partes principais do framework Spring.

É interessante ver como, sob a complexidade, Spring não faz nenhuma magia negra - apenas muita contabilidade, encanamento, tratamento de exceções necessários e assim por diante

5. Conclusões

Neste tutorial rápido, vimos como interagir programaticamente com transações em testes baseados em Spring.

A implementação de todos esses exemplos pode ser encontrada emthe GitHub project - este é um projeto Maven, portanto, deve ser fácil de importar e executar como está.