Transacción de reversión después de @Test

85

En primer lugar, encontré muchos hilos en StackOverflow sobre esto, pero ninguno de ellos realmente me ayudó, así que lamento hacer una pregunta posiblemente duplicada.

Estoy ejecutando pruebas JUnit usando spring-test, mi código se ve así

@RunWith(SpringJUnit4ClassRunner.class)  
@ContextConfiguration(locations = {})
public class StudentSystemTest {

    @Autowired
    private StudentSystem studentSystem;

    @Before
    public void initTest() {
    // set up the database, create basic structure for testing
    }

    @Test
    public void test1() {
    }    
    ...  
}

Mi problema es que quiero que mis pruebas NO influyan en otras pruebas. Así que me gustaría crear algo como una reversión para cada prueba. He buscado mucho esto, pero hasta ahora no he encontrado nada. Estoy usando Hibernate y MySql para esto

Jan Vorcak
fuente
¿A qué te refieres con reversión? ¿Limpiar la base de datos?
Gaurav
5
configurándolo exactamente en el mismo estado en el que estaba después de la ejecucióninitTest
Jan Vorcak

Respuestas:

127

Simplemente agregue una @Transactionalanotación en la parte superior de su prueba:

@RunWith(SpringJUnit4ClassRunner.class)  
@ContextConfiguration(locations = {"testContext.xml"})
@Transactional
public class StudentSystemTest {

Por defecto, Spring iniciará una nueva transacción en torno a su método de prueba y @Before/ @Aftercallbacks, retrocediendo al final. Funciona por defecto, basta con tener algún administrador de transacciones en el contexto.

De: 10.3.5.4 Gestión de transacciones (negrita mía):

En el marco TestContext, las transacciones son administradas por TransactionalTestExecutionListener. Tenga en cuenta que TransactionalTestExecutionListenerestá configurado de forma predeterminada , incluso si no declara explícitamente @TestExecutionListenersen su clase de prueba. Sin embargo, para habilitar el soporte para transacciones, debe proporcionar un PlatformTransactionManagerbean en el contexto de la aplicación cargado por @ContextConfigurationsemántica. Además, debe declarar @Transactionala nivel de clase o método para sus pruebas .

Tomasz Nurkiewicz
fuente
2
bueno, probé esto antes, y todavía no funciona, tal vez ... ¿El problema puede ser que no definí el PlatformTransactionManager, cómo puedo hacer eso?
Jan Vorcak
@javo: ¿cómo estás modificando la base de datos? Si está utilizando Jpa / Hibernate / JdbcTemplate / ... debe haber algunos PlatformTransactionManager. De lo contrario, ¿cómo sabrá Spring sobre sus transacciones y su base de datos?
Tomasz Nurkiewicz
1
El enlace en esta respuesta ya no es correcto; consulte la respuesta de user2418306 a continuación para obtener el enlace correcto y más contexto de la documentación de Spring.
DaveyDaveDave
1
No funciona, cada método requiere una transacción separada, no puedo hacerlo para toda la clase
Kamil Nekanowicz
Estoy probando un inserto en una mesa. Con @Transactional, el comando de inserción ni siquiera se emite contra la base de datos (puedo ver eso porque la consola tiene hibernate show-sql true). Funciona bien en muchos casos. Pero una de mis pruebas verifica que la base de datos emite una excepción DataAccessException debido a una restricción de la base de datos. En este caso, la prueba falla porque la reversión ocurre "en la memoria" y nunca se llama a la base de datos. Solución: utilícelo @Transactionala @Testnivel de método y no a nivel de clase.
Paulo Merson
17

Aparte: el intento de enmendar la respuesta de Tomasz Nurkiewicz fue rechazado:

Esta edición no hace que la publicación sea ni un poco más fácil de leer, más fácil de encontrar, más precisa o más accesible. Los cambios son completamente superfluos o perjudican activamente la legibilidad.


Enlace correcto y permanente a la sección de documentación relevante sobre pruebas de integración.

Para habilitar el soporte para transacciones, debe configurar un PlatformTransactionManagerbean en el ApplicationContextque se carga mediante @ContextConfigurationsemántica.

@Configuración
@PropertySource ("application.properties")
Persistencia de clase pública {
    @Autowired
    Medio ambiente env;

    @Frijol
    DataSource dataSource () {
        devolver nuevo DriverManagerDataSource (
                env.getProperty ("datasource.url"),
                env.getProperty ("datasource.user"),
                env.getProperty ("datasource.password")
        );
    }

    @Frijol
    PlatformTransactionManager transactionManager () {
        devolver nuevo DataSourceTransactionManager (dataSource ());
    }
}

Además, debe declarar la @Transactionalanotación de Spring ya sea a nivel de clase o método para sus pruebas.

@RunWith (SpringJUnit4ClassRunner.class)
@ContextConfiguration (classes = {Persistence.class, SomeRepository.class})
@Transaccional
clase pública SomeRepositoryTest {...}

Anotar un método de prueba con @Transactionalhace que la prueba se ejecute dentro de una transacción que, de forma predeterminada, se revertirá automáticamente después de completar la prueba. Si se anota una clase de prueba @Transactional, cada método de prueba dentro de esa jerarquía de clases se ejecutará dentro de una transacción.

usuario2418306
fuente
12

Las respuestas que mencionan la adición @Transactionalson correctas, pero para simplificar, podría tener su clase de prueba extends AbstractTransactionalJUnit4SpringContextTests.

mate b
fuente
1
agregar la anotación '@Transactional' a nivel de clase no funciona, agregar la anotación '@Transactional' por separado para cada función funciona, y extiende AbstractTransactionalJUnit4SpringContextTests también funciona
Kamil Nekanowicz
5

Lo sé, llegué demasiado tarde para publicar una respuesta, pero espero que pueda ayudar a alguien. Además, acabo de resolver este problema que tenía con mis pruebas. Esto es lo que tuve en mi prueba:

Mi clase de prueba

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "path-to-context" })
@Transactional
public class MyIntegrationTest 

Xml de contexto

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
   <property name="driverClassName" value="${jdbc.driverClassName}" />
   <property name="url" value="${jdbc.url}" />
   <property name="username" value="${jdbc.username}" />
   <property name="password" value="${jdbc.password}" />
</bean>

Todavía tenía el problema de que la base de datos no se limpiaba automáticamente.

El problema se resolvió cuando agregué la siguiente propiedad a BasicDataSource

<property name="defaultAutoCommit" value="false" />

Espero eso ayude.

Atul Kumbhar
fuente
Bueno, ¿confirma sus declaraciones manualmente? ¿Está seguro de que sus datos se escribieron en su base de datos?
franta kocourek
Para cualquiera que tenga dificultades para comprender Spring Transactions, asegúrese de que su fuente de datos NO esté configurada para autocomprimirse o se volverá loco tratando de averiguar por qué @Transactional parece no hacer nada.
Joe Ernst
2

Debe ejecutar su prueba con un contexto Spring y un administrador de transacciones, por ejemplo,

@RunWith(SpringJUnit4ClassRunner.class)  
@ContextConfiguration(locations = {"/your-applicationContext.xml"})
@TransactionConfiguration(transactionManager="txMgr")
public class StudentSystemTest {

     @Test
     public void testTransactionalService() {
         // test transactional service
     }

     @Test
     @Transactional
     public void testNonTransactionalService() {
         // test non-transactional service
     }
}

Consulte el capítulo 3.5.8. Transaction Managementde la referencia de Spring para obtener más detalles.

Johan Sjöberg
fuente
0

Además de agregar @Transactionalun @Testmétodo, también debe agregar@Rollback(false)

usuario2615724
fuente
-4

Puede deshabilitar la reversión:

@TransactionConfiguration(defaultRollback = false)

Ejemplo:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@Transactional
@TransactionConfiguration(defaultRollback = false)
public class Test {
    @PersistenceContext
    private EntityManager em;

    @org.junit.Test
    public void menge() {
        PersistentObject object = new PersistentObject();
        em.persist(object);
        em.flush();
    }
}
DwD
fuente
6
Eso es exactamente lo contrario de lo que pide el OP
Adam Michalik
Esto debería haber sido un comentario a la respuesta aceptada.
DerMike