#Transactional does not rollback in JUnit test - java

I have 2 JUnit tests in the same test class each one marked as #Transactional, the first one create users and the second one create users and check that we can get these users.
The problem is that when the second test runs, users created by the first test is still in the DB.
#Test
#Transactional
public void testCreateUser() throws Exception
#Test
#Transactional
public void testGetUserById() throws Exception
Do you have any idea why #Transactional is not working well in the same test class? Furthermore data is rolled back at the end of the test class.
I'm using Spring boot + Mysql + Hibernate.
Thank you for your time.

Try something like this and take care about classpath of your ...context.xml.
You can also move #Transactional above class if you want every method to be transactional or above each method you want.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"classpath:/config/your-context.xml","classpath:/config/ehcache.xml"})
#Transactional
#WebAppConfiguration
public class IntegrationTests {
#Autowired
private ApplicationContext appContext;
#Autowired
public FileDao fileDao;
#Autowired
public SessionFactory sessionFactory;
#Test
public void test1() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException{
....
....
....
}
}

Add to configuration class #EnableTransactionManagement

Related

#DataMongoTest fails because of UnsatisfiedDependencyException

I'd like to test my repository method. However when I run my test, it fails because of UnsatisfiedDependencyException. It for some reason tries to create AuthorizationServerConfig (or other bean if I remove #Configuration annotation from this one). It fails cause deeper in dependencies chain it requires RabbitMQ connection pool, that I prefer to not provide in repository test.
The question is why Spring tries to create all those beans not linked to repository logic?
I attempted to exclude all those beans with #DataMongoTest(exludeFilters: ...) and #DataMongoTest(exludeAutoConfiguration: ...) but it had no effect.
The only thing that helped was to add #Profile("!test") to all beans (all! controllers, services, components) in an application, but it smells like a very ugly solution.
Repository class is very simple:
#Repository
public interface ParticipantRepository extends MongoRepository<Participant, String> {
List<Participant> findAllByLoggedInTrueAndAccessTokenExpirationAfter(Date expirationAfter);
}
My test class:
#DataMongoTest()
#RunWith(SpringRunner.class)
public class ParticipantRepositoryTest {
#Autowired
private MongoTemplate mongoTemplate;
#Autowired
private ParticipantRepository repository;
private List<Participant> participants;
#Before
public void setUp() throws Exception {
participants = createParticipants();
repository.saveAll(participants);
}
#After
public void tearDown() throws Exception {
repository.deleteAll();
}
#Test
public void findAllByLoggedInTrueAndExpirationAfter_shouldNotReturnLoggedOutParticipants() {
List<Participant> result = repository.findAllByLoggedInTrueAndAccessTokenExpirationAfter(new Date());
getLoggedOutParticipants().forEach(participant -> assertThat(participant, not(isIn(result))));
}
...
}

Using #PostConstruct in a test class causes it to be called more than once

I am writing integration tests to test my endpoints and need to setup a User in the database right after construct so the Spring Security Test annotation #WithUserDetails has a user to collect from the database.
My class setup is like this:
#RunWith(value = SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
#WithUserDetails(value = "email#address.com")
public abstract class IntegrationTests {
#Autowired
private MockMvc mockMvc;
#Autowired
private Service aService;
#PostConstruct
private void postConstruct() throws UserCreationException {
// Setup and save user data to the db using autowired service "aService"
RestAssuredMockMvc.mockMvc(mockMvc);
}
#Test
public void testA() {
// Some test
}
#Test
public void testB() {
// Some test
}
#Test
public void testC() {
// Some test
}
}
However the #PostConstruct method is called for every annotated #Test, even though we are not instantiating the main class again.
Because we use Spring Security Test (#WithUserDetails) we need the user persisted to the database BEFORE we can use the JUnit annotation #Before. We cannot use #BeforeClass either because we rely on the #Autowired service: aService.
A solution I found would be to use a variable to determine if we have already setup the data (see below) but this feels dirty and that there would be a better way.
#PostConstruct
private void postConstruct() throws UserCreationException {
if (!setupData) {
// Setup and save user data to the db using autowired service "aService"
RestAssuredMockMvc.mockMvc(mockMvc);
setupData = true;
}
}
TLDR : Keep your way for the moment. If later the boolean flag is repeated in multiple test classes create your own TestExecutionListener.
In JUnit, the test class constructor is invoked at each test method executed.
So it makes sense that #PostConstruct be invoked for each test method.
According to JUnit and Spring functioning, your workaround is not bad. Specifically because you do it in the base test class.
As less dirty way, you could annotate your test class with #TestExecutionListeners and provide a custom TestExecutionListener but it seem overkill here as you use it once.
In a context where you don't have/want the base class and you want to add your boolean flag in multiple classes, using a custom TestExecutionListener can make sense.
Here is an example.
Custom TestExecutionListener :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
public class MyMockUserTestExecutionListener extends AbstractTestExecutionListener{
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
MyService myService = testContext.getApplicationContext().getBean(MyService.class);
// ... do my init
}
}
Test class updated :
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
#WithUserDetails(value = "email#address.com")
#TestExecutionListeners(mergeMode = MergeMode.MERGE_WITH_DEFAULTS,
value=MyMockUserTestExecutionListener.class)
public abstract class IntegrationTests {
...
}
Note that MergeMode.MERGE_WITH_DEFAULTS matters if you want to merge TestExecutionListeners coming from the Spring Boot test class with TestExecutionListeners defined in the #TestExecutionListeners of the current class.
The default value is MergeMode.REPLACE_DEFAULTS.

Spring Boot tests. Initilize database programatically

I develope a WebApp using Spring Boot. I need to make integration tests with database. I have a problem in database initilization. I know it is possible to prepare database with initilazion scripts. And I do it partially. But some records have type of BLOB and it is annoying to initilize it by the script. So I'm trying to init this records programatically from #Test method using CrudRepository implementations from ApplicationContext (that are encapsulated in PersistenceService).
#TestExecutionListeners({DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class, DbUnitTestExecutionListener.class})
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#DatabaseSetup(MyTest.DATASET)
#DatabaseTearDown(type = DatabaseOperation.DELETE_ALL, value = { MyTest.DATASET })
#DirtiesContext
public class MyTest {
protected static final String DATASET = "classpath:dbunit/customer.xml";
#Autowired
private TestRestTemplate restTemplate;
#Autowired
private PersistenceService persistenceService;
#Test
#Transactional
#Rollback(false)
public void afterGroupDeleteCrlShouldContainsAllCertificates() throws Exception {
prepareDatabase(persistenceService);
restTemplate.delete("/customer/");
}
But marking of #Test method by #Transactional is the reason of deadlock when I'm calling restTemplate.delete() because there is uncommited transaction.
So I'm trying to commit transaction manually adding after calling of prepareDatabase(persistenceService) this snippet:
if (TestTransaction.isActive()) {
TestTransaction.flagForCommit();
TestTransaction.end();
}
This snippet fixes deadlock but generate SQLException after test execution
java.sql.SQLException: PooledConnection has already been closed.
I'm sure it is common task. But I don't know how to resolve it gracefully.
I think I'm missing some parts, but how about putting the prepareDatabase into a #Before method in your test.
#Before
public void setupDatabaseForEachTest(){
prepareDatabase(persistenceService);
}
I think that will allow your #Test to be #Transactional and give you the desired results.

Transaction roll back is not working in test case in #Nested class of JUnit5

I use spring-boot, JUnit5, Mybatis.
#SpringJUnitJupiterConfig(classes = {RepositoryTestConfig.class})
#MapperScan
#Rollback
#Transactional
public class TestClass {
#Autowired
private TestMapper testMapper;
#BeforeEach
void init() {
User user = new User();
testMapper.insert(user);
}
#Test
public void test1() {
// (1) success rollback
}
#Nested
class WhenExistData {
#Test
public void test2() {
// (2) rollback not working
}
}
}
(1) is working rollback. And the following log is output.
2017-05-26 22:21:29 [INFO ](TransactionContext.java:136) Rolled back transaction for test context ...
But, (2) is not working. I want to be able to roll back into #Nested.
This is to be expected: the Spring TestContext Framework has never supported "inheritance" for nested test classes.
Thus your "work around" is actually the correct way to achieve your goal at this point in time.
Note, however, that I may add support for "pseudo-inheritance" for nested test classes in conjunction with SPR-15366.
Regards,
Sam (author of the Spring TestContext Framework)
I solved it in the following way..
#SpringJUnitJupiterConfig(classes = {RepositoryTestConfig.class})
#MapperScan
#Rollback
#Transactional
public class TestClass {
#Autowired
private TestMapper testMapper;
#BeforeEach
void init() {
User user = new User();
testMapper.insert(user);
}
#Nested
#SpringJUnitJupiterConfig(classes = {RepositoryTestConfig.class})
#MapperScan
#Rollback
#Transactional
class WhenExistData {
#Test
public void test2() {
}
}
}
I solved it in the following way
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
// JUnit5
#SpringBootTest
public class TestClass {
#Resource
private TestMapper testMapper;
#Test
#Rollback
#Transactional
public void createByTimerId() {
Assertions.assertEquals(1, testMapper.insert());
}
}
Using the annotation NestedTestConfiguration from Spring on the enclosing class did the trick for me. It seems that without it, the nested class won't inherit the config.
#NestedTestConfiguration(NestedTestConfiguration.EnclosingConfiguration.INHERIT)
So maybe on your TestClass you would have your current annotations and then:
#NestedTestConfiguration(NestedTestConfiguration.EnclosingConfiguration.INHERIT)
public class TestClass {
...

JUnit rollback transaction with #Async method

I am writing an integration test using SpringJUnit4ClassRunner.
I have a base class:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration({ /*my XML files here*/})
#Ignore
public class BaseIntegrationWebappTestRunner {
#Autowired
protected WebApplicationContext wac;
#Autowired
protected MockServletContext servletContext;
#Autowired
protected MockHttpSession session;
#Autowired
protected MockHttpServletRequest request;
#Autowired
protected MockHttpServletResponse response;
#Autowired
protected ServletWebRequest webRequest;
#Autowired
private ResponseTypeFilter responseTypeFilter;
protected MockMvc mockMvc;
#BeforeClass
public static void setUpBeforeClass() {
}
#AfterClass
public static void tearDownAfterClass() {
}
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).addFilter(responseTypeFilter).build();
}
#After
public void tearDown() {
this.mockMvc = null;
}
}
Then I extend it and create a test using mockMvc:
public class MyTestIT extends BaseMCTIntegrationWebappTestRunner {
#Test
#Transactional("jpaTransactionManager")
public void test() throws Exception {
MvcResult result = mockMvc
.perform(
post("/myUrl")
.contentType(MediaType.APPLICATION_XML)
.characterEncoding("UTF-8")
.content("content")
.headers(getHeaders())
).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_XML))
.andExpect(content().encoding("ISO-8859-1"))
.andExpect(xpath("/*[local-name() ='myXPath']/")
.string("result"))
.andReturn();
}
In the end of the flow, an entity is saved into DB. But the requirement here is that is should be done asynchronously. So consider this method is called:
#Component
public class AsyncWriter {
#Autowired
private HistoryWriter historyWriter;
#Async
public void saveHistoryAsync(final Context context) {
History history = historyWriter.saveHistory(context);
}
}
Then HistoryWriter is called:
#Component
public class HistoryWriter {
#Autowired
private HistoryRepository historyRepository;
#Transactional("jpaTransactionManager")
public History saveHistory(final Context context) {
History history = null;
if (context != null) {
try {
history = historyRepository.saveAndFlush(getHistoryFromContext(context));
} catch (Throwable e) {
LOGGER.error(String.format("Cannot save history for context: [%s] ", context), e);
}
}
return history;
}
}
The problem with all this is that after test is done, History object is left in the DB. I need to make test transaction to rollback all changes in the end.
Now, what I've tried so far:
Remove #Async annotation. Obviously, this cannot be the solution, but was done to confirm rollback will be perform without it. Indeed, it is.
Move #Async annotation to HistoryWriter.saveHistory() method to have it in one place with #Transactional. This article https://dzone.com/articles/spring-async-and-transaction suggests it should work this way, but for me, no rollback is done after test.
Swap places of these two annotations. It does not give the desired result as well.
Does anyone have any idea how to force rollback of the DB changes made in asynchronous method?
Side notes:
Transaction configuration:
<tx:annotation-driven proxy-target-class="true" transaction- manager="jpaTransactionManager"/>
Async configuration:
<task:executor id="executorWithPoolSizeRange" pool-size="50-75" queue-capacity="1000" />
<task:annotation-driven executor="executorWithPoolSizeRange" scheduler="taskScheduler"/>
Does anyone have any idea how to force rollback of the DB changes made in asynchronous method?
That is unfortunately not possible.
Spring manages transaction state via ThreadLocal variables. A transaction started in another thread (e.g., the one created for your #Async method invocation) can therefore not participate in a transaction managed for the parent thread.
This means that the transaction used by your #Async method is never the same as the test-managed transaction which gets automatically rolled back by the Spring TestContext Framework.
Thus, the only possible solution to your problem is to manually undo the changes to the database. You can do this using JdbcTestUtils to execute an SQL script programmatically within an #AfterTransaction method, or you can alternatively configure an SQL script to be executed declaratively via Spring's #Sql annotation (using the after execution phase). For the latter, see How to execute #Sql before a #Before method for details.
Regards,
Sam (author of the Spring TestContext Framework)

Categories