Spring boot integration test rollback does not work - java

In spring boot I'm trying to create my first transactional test, but the trasaction doesn't work.
#TestPropertySource(locations = "classpath:test.properties")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#RunWith(SpringJUnit4ClassRunner.class)
//#RunWith(SpringRunner.class)
#Transactional
#TestExecutionListeners(
mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
listeners = {TransactionalTestExecutionListener.class}
)
public class IntegrationTests
{
#Autowired
TemperatureLogRepository temperatureLogRepository;
#Test
#SqlGroup({
#Sql(
executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
config = #SqlConfig(transactionMode = ISOLATED),
scripts = "classpath:sqls/insertRecords2.sql"
)
})
public void firstRepoTest() throws SQLException
{
assertThat(temperatureLogRepository.getFullList().size()).isEqualByComparingTo(0);
}
}
I know that SqlGroup is not necessary, but there will be more files added.
That I have now:
SQL file executed well and inserted to the DB.
The getFullList() method can read it and returns with the right data.
After the test I still have the data in the DB, there is no rollback on the transaction.
I'm not exactly sure they are running in the same transaction. Is it possible to be the data commited to the db before the getFullList() method run?
What I need:
#Sql inserts data to the transaction.
getFullList() read the data from the transaction.
Test the returned data.
Rollback the transaction.

From Spring Testing - Executing SQL scripts declaratively with #Sql:
Script execution phases
By default, SQL scripts will be executed before the corresponding test
method. However, if a particular set of scripts needs to be executed
after the test method — for example, to clean up database state — the
executionPhase attribute in #Sql can be used as seen in the following
example. Note that ISOLATED and AFTER_TEST_METHOD are statically
imported from Sql.TransactionMode and Sql.ExecutionPhase respectively.
#Test
#Sql(
scripts = "create-test-data.sql",
config = #SqlConfig(transactionMode = ISOLATED) ) #Sql(
scripts = "delete-test-data.sql",
config = #SqlConfig(transactionMode = ISOLATED),
executionPhase = AFTER_TEST_METHOD )
public void userTest {
// execute code that needs the test data to be committed
// to the database outside of the test's transaction
}
Related question: How to execute #Sql before a #Before method
UPDATE
Remove #SqlConfig from #Sql:
config = #SqlConfig(transactionMode = ISOLATED)
Or change to:
config = #SqlConfig(transactionMode = TransactionMode.INFERRED)
SQL script runs in separate transaction which is not roll backed:
org.springframework.test.context.jdbc.SqlConfig.TransactionMode.ISOLATED
Indicates that SQL scripts should always be executed in a new,
isolated transaction that will be immediately committed.

Related

Spring Boot Test not rolling back transactions (MS SQL Server, Spring Boot 2.7.5)

I am writing integration tests for a project with multiple datasources and want to test the repository and service layer. Problem is that the #Transactional annotation is not working and data inserted during tests is persisted.
This is a simple IT. I need to use #SpringBootTest instead of #DataJpaTest because multiple datasources are configured in a bean which handles assigning the right datasource to the correct repository.
#SpringBootTest(classes = { MyApp.class, TestSecurityConfiguration.class })
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#ActiveProfiles("database-test")
#Transactional
#Rollback
public class MyIT {
#Autowired
ChannelRepository channelRepository;
#Test
public void testChannels() {
Channel testChannel1 = new Channel();
testChannel1.setDescription("Test Channel 1");
channelRepository.save(testChannel1);
Channel testChannel2 = new Channel();
testChannel2.setDescription("Test Channel 2");
channelRepository.save(testChannel2);
Channel channel = channelRepository.findById(1).get();
assertThat(channel).isNotNull();
}
}
The dialect configured in the application.yml is "org.hibernate.dialect.SQLServer2012Dialect" and the database is our test database that runs on a dedicated server, not an embedded database.
The data is inserted without rollback.
After looking at the JpaTransactionManager logs using:
logging:
level:
sql: DEBUG
ROOT: DEBUG
org.springframework.orm.jpa: DEBUG
org.springframework.transaction: DEBUG
I saw that for each .save() call there was a new inner transaction. I tried to specify the transaction manager manually by passing the value to the #Transactional annotation and now it works. Looks like the DataSource beans are not picked up entirely correct. I got the idea based on this article where a ChainedTransactionManager is used for multiple datasources https://medium.com/preplaced/distributed-transaction-management-for-multiple-databases-with-springboot-jpa-and-hibernate-cde4e1b298e4

Spring Boot Integration Test for Service method annotated with #Transactional(propagation = Propagation.REQUIRES_NEW)

I'm using Spring Boot and trying to write an integration test in which a method annotated: #Transactional(propagation = Propagation.REQUIRES_NEW) is called.
My test method is annotated:
#Test
#Transactional
#Sql(scripts = {
"/sql/insert_some_records.sql"
}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
With this configuration I'm getting a strange behaviour and when I tried to debug I found out that the service bean class method I'm trying to test can't find the records I've inserted using the sql script in the #Sql i.e.
myRepo.findAll() inside the method gets an empty list.
I've managed to run the test after changing the propagation option for the test method to:
#Transactional(propagation = Propagation.NEVER)
or:
#Transactional(propagation = Propagation.SUPPORTS)
And the method in the service class can correctly find all records from DB, but the drawback is that I have to manually delete these inserted records by the script after the test.
I've read Spring documentation about Propagation option and still really confused.
Can anyone explain for me what's the cause of the problem? And why everything works after changing the propagation option?
when test run, it create a transaction T1 by default if with annotation #Trasactional, and your service method create another transaction T2 per REQUIRE_NEW, since T1 has not commit util test over, T2 can not see the data that has not been committed. but if test is without #Transactional , or NEVER or SUPPORT, it will not create a transaction, but the sql was execute by jdbcTemplate.execute(), it will create a T1 and commit immediately after execute() return. so service method is able to see the data.

Prevent Spring bean re-initialization for each JUnit test

I have integration tests based on JUnit that access DB. We also use Liquibase Spring bean to init database.
If I try running multiple tests in parallel each of them tries to initialize DB using Liquibase causing locks and eventually failures since only one instance of Liquibase can modify DB at a time.
The tests are configured as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#Transactional
#WebAppConfiguration
#Sql({"/schema/insert-test-data.sql"})
How can I configure DB initialization (schema and data) that it will be done only once and not for each test?
This is just idea not testet, but if you are not executing scripts in parallel what about using ScriptUtils or ResourceDatabasePopulator iside #Before method with some switch.
#Before
public void init(){
if (wasInitialized)
return;
new ResourceDatabasePopulator(new ClassPathResource("path/to/sql.sql")).execute(dataSource);
wasInitialized = true;
}

#Sql in Spring integration test is suspending transaction for AFTER_TEST_METHOD

I have the following situation:
#Test
#Sql(scripts = "before.sql" , executionPhase = BEFORE_TEST_METHOD, config = #SqlConfig(transactionMode = ISOLATED))
#Sql(scripts = "cleanUp.sql", executionPhase = AFTER_TEST_METHOD , config = #SqlConfig(transactionMode = ISOLATED))
I need the test data to be committed before the test begin(it is obligatory to be committed), and after that to clean up that data.
The problem is, when AFTER_TEST_METHOD is executed it is suspending the current transaction in order to make a new one (because of ISOLATED), so if my mockMvc.perform(...) is locking the test data the test cannot finish because the isolated transaction is waiting for lock.
If I use INFERRED, the clean up is reusing the existing transaction which is roll-baked from the Spring transaction manager, so the committed data from BEFORE_TEST_METHOD stays.
Is there a some way to resolve this, some configuration that I'm missing ?
Thanks.
P.S I'm using testNG

Spring/DBUnit dont rollback transaction until the end of test

We are using dbunit with spring boot and H2 database for local unit/integration testing. In the test we are using TransactionDbUnitTestExecutionListener and #Transactional annotations so that the transaction during the test does not rollback until the end.
However, I observed that if in the test I call a function with Propogation.REQUIRES_NEW, the rollback occurs immediately after the function returns and then test fails because it cannot find the data.
Here is the example code
#Test
#Transactional
#DatabaseSetup("/Uploads.xml")
#Rollback(value = false)
fun testStampSidekiqJobId() {
uploadEntity = uploadService.stampSidekiqJobId(uploadEntity, "fakeJobId")
// test values
}
// here is the called function
#Transactional(propagation = Propagation.REQUIRES_NEW)
open fun stampSidekiqJobId(uploadEntity:UploadEntity, encodeJobId:String): UploadEntity {
I have tried with and without Rollback annotation. Please note that the class hosting this unit test has DependencyInjectionTestExecutionListener, TransactionDbUnitTestExecutionListener annotations.

Categories

Resources