How to get JPA context in Springboot integration test? - java

I am running a Springboot application with jpa. I am trying to set up an integration-test based on Cucumber. When in my test I try to access the repo, I get an 'org.hibernate.LazyInitializationException' (With a message "No Session".). This only happens in my integration-test, but not in the real application. A workaround would be to put #Transactional on the method which does the call, but this does not work if I execute in new threads.
My first question would be: Why doesn't it work withouth #Transactional annotation?
My second would be: Why doesn't it work with #Transactional annotation in new threads?
Here is a simplified version of my code:
The cucumber test:
#RunWith(Cucumber.class)
#CucumberOptions(features = "src/test/resources/some-integration-test.feature")
public class IntegrationTests {}
The cucumber-steps:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
public class IntegrationSteps {
#Autowired
SomeRepo repo;
#When("two updates happen at the same time")
public void twoUpdatesHappenAtTheSameTime() {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.execute(set("Thread 1"));
executorService.execute(set("Thread 2"));
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.MINUTES);
}
public void set(String someThing) {
Some some = repo.getOne(1234);
repo.setSomeThing(someThing);
repo.save(some);
}
}
And the repo:
#Repository
public interface SomeRepo extends JpaRepository<Some, Integer> {}

The problem seems to be with getOne(). With getOne() you only get a reference to the entity. No real call to the database is executed until one tries to access the entity's fields.
Normally these fields are loaded lazy when one uses getOne(), but for some reason (which is still unclear to me) this does not work in the SpringBootTest.
I've found two solutions for this issue:
Annotate the test with #Transactional. This way you will have a context to load the entity. The downside is that you still don't seem to get the most recent version of your entity. In my case I updated the entity in my code, while in test code the update was not present in the entity (even though update happened before the getOne() call).
Don't use getOne(), but findById() instead. The downside is that findById() is eagerly loaded, but as we only use this for our test, it will not affect the performance of our app.

Related

Rollback not working on Spring Boot #Transactional method for H2 database

Please give me tips to get my failing JUnit test to pass in my Spring Boot app.
I have a Service that needs to delete rows from tables A and B (from a H2 database) in a transactional manner such that: if A is modified and then an exception occurs before B is modified, then neither A nor B are modified. However, even after 
Adding #Transactional to the PUBLIC Service::deleteRows method, and
Adding #EnableTransactionManagement to the #Configuration class that produces the JdbcTemplate that all repositories use, and
Confirming that spring-tx is on the classpath
the transaction still doesn't rollback.
Here's the failing JUnit testcase
// Service.java
#Transactional
public void deleteRows() {
aRepository.deleteRow(123);
bRepository.deleteRow(123);
}
// ServiceTest.java
#Test
void test() {​
// first ensure that an exception is thrown when bRepository.deleteRow() is called
// FYI deleteRow() calls jdbcTemplate.update()
    doThrow(RuntimeException.class)
        .when(mockJdbcTemplate)
        .update(any(), eq(123));
    BRepository mockBRepository = new BRepository(mockJdbcTemplate);
    Service service = new Service(realARepository, mockBRepository);
    assertTableHasRows(1, "TABLE_A"); // before calling the service, TABLE_A has 1 row
    assertThrows(RuntimeException.class, service::deleteRows);    
    assertTableHasRows(1, "TABLE_A"); // TABLE_A should still have 1 row when an exception occurs during the tx
}​
Remove #Transactional and run test again.
In the end the problem was solved by NOT using mocking to trigger an exception. Instead, we set up the database schema so that normal operation of service::deleteRows would trigger an exception.

Spring Data JPA database changes after each method don't roll back in integration tests

I am using JUnit 5 with Spring boot 2.2.4 for integration testing my Spring Boot application code with MySQL 5.7 database but the changes that a previous test method makes to database aren't rolled back causing errors in the ones that follow.
I've tried answers from different stackoverflow posts such as adding #Transactional (from Spring), #Rollback (method as well as class level), #TestExecutionListeners(listeners = {TransactionalTestExecutionListener.class}). I've tested these options individually (as well as in combination) but adding them in the test code together.
This is my test code:
#DataJpaTest
#Transactional
#Rollback
#ExtendWith(SpringExtension.class)
#Import({UserUsecasesImpl.class})
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class UserUsecasesImplINTTest {
#Autowired
private TestEntityManager entityManager;
#Autowired
#Spy
private UserRepository userRepository;
#Autowired
private UserUsecases userUsecases;
#Test
void injectedComponentsAreNotNull(){
assertNotNull(entityManager);
assertNotNull(userRepository);
assertNotNull(userUsecases);
}
#Test
#Rollback
void Create_EntityAlreadyExists_ReturnsException() {
// set up
User expected = UserFixture.user1.toBuilder().build();
// entityManager.persistAndFlush(expected);
userRepository.saveAndFlush(expected);
// execute
User actual = userUsecases.create(expected);
// verify
assertEquals(expected, actual);
verify(userRepository).findOne(any(Example.class));
verify(userRepository, never()).save(any(User.class));
verifyNoMoreInteractions();
}
#Test
void Find_WhenUserPresent_ReturnUser() {
// // set up
User expected = UserFixture.user1.toBuilder().build();
entityManager.persistAndFlush(expected);
// execute
User actual = userRepository.find(expected);
// verify
assertEquals(expected, actual);
}
#Test
// #Transactional
void Find_WhenUserNotPresent_ReturnNull() {
// // set up
User expected = UserFixture.user1.toBuilder().build();
// entityManager.persistAndFlush(UserFixture.user2.toBuilder().build());
// execute
User actual = userUsecases.find(expected);
// verify
assertNull(actual);
}
}
UserUsecasesImpl is the class that I am trying to test which contains usecases such as creating a user, finding a user, get all users, etc. There is no special configuration anywhere else in the code.
Please implement following fixes and let me know if problem still there:
Remove all #Transactional and #Rollback. Keep #DataJpaTest which by default makes all of your tests #Transactional and therefore by default will roll them all back.
don't use saveAndFlush. The flush part is flushing save operation into the database and most likely causes your issue. Use save instead. More here https://www.baeldung.com/spring-data-jpa-save-saveandflush
If you are expecting exception assert it with assertThrows.
In general try not to throw every single annotation you know into the mix at the same time. Add them one at a time as you need them, try to understand what they do.
What is UserUseCases and why does it do most of the logic I would expect the Repository class to do?

How to execute sql script before and after each test method

There is an #Sql annotation in spring which allows to execute sql code before and after the test method:
#Test
#Sql("init.sql")
#Sql(scripts = "clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void someTest()
{
}
However I have several test methods where I would like to provide the same clean environment like in the test above and I don't want to repeat for every test the same #Sql annotation. How to do it once for all methods? For example:
// JPA and Spring other test annotations
#Sql("init.sql")
#Sql(scripts = "clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public class TestClass
{
// init.sql executed before every test, clean.sql executed after every test
}
Indeed when you place #Sql on the class, sql scripts will be executed before and after every test defined in that class, more specifically before #Before and after #After methods. So,
// JPA and Spring other test annotations
#Sql("init.sql")
#Sql(scripts = "clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public class TestClass
{
// init.sql executed before every test, clean.sql executed after every test
}
is going to work according to the #Sql definition:
#Target({ElementType.TYPE, ElementType.METHOD})
/// other annotations
public #interface Sql {
//
}
If my understanding is correct, you want to execute an init script to put the DB in a certain state, and then ensure the DB is back to that state before each test method, right?
The simplest solution is to use #Transactional, then. By default, Spring Boot will automatically roll back the test transaction of a #Transactional-annotated test, thus resetting the DB to the original state.
There are two downsides one should consider, though:
#Transactional means there will exist a transaction spanning the entire execution of the test method which the tested service methods will typically join. Hence, the test itself cannot be relied upon to verify the correctess of transactional boundaries in the production code (LazyInitializationExceptions may be covered by this 'outer' transacion, for example)
The persistence context will not flush unless necessary, meaning that some issues (e.g. DB constraint violations) will not surface. I tend to work around that issue using a last-chance flush like so:
#After
public void flushContext() {
if (TransactionSynchronizationManager.isActualTransactionActive()) {
entityManager.flush();
}
}

How to cleanup h2 db after each junit test?

I am making junit tests which adds some users before each test case. The code is:
#BeforeEach
void setUp() {
saveUser();
saveEntry();
}
#Test
void saveUser() {
User user = new User();
user.setUserId(null);
user.setUsername("John");
user.setEmail("john#foo.com");
user.setPassword("password");
userService.saveUser(user);
}
#Test
void saveEntry() {
Entry entry = new Entry();
entry.setText("test text");
entry.setUserId(1L);
entryService.saveEntry(entry);
}
As you see I am using the methods that I have in my service layer to create entries and users. If I run the tests one by one there is no problem. But when I run all tests then db is not returning 1 item and returning multiple items so exception occurs.
I need to cleanup h2 db after each test with maybe #AfterEach annotation but I do not have and delete method in my code to invoke. How can I cleanup the H2 db ?
In addition to #J Asgarov answer which is correct providing you use spring-boot if you want to perform some actions before and after each test (more specifically before #Before and after #After methods) you can use #Sql annotation to execute specific sql script for example from test resources.
#Sql("init.sql")
#Sql(scripts = "clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public class TestClass
}
It might be handy for sequences, since they don't care of rollback.
Regarding #Transactional mentioned by #Mark Bramnik be careful, because the transaction spans for entire test method, so you cannot verify correctness of the transaction boundaries.
You've mentioned spring and it looks like you're testing DAO layer, so I assume the tests are running with SpringExtension/SpringRunner if you're on junit 4.
In this case,
Have you tried to use #Transactional on the test method? Or alternatively if all the test methods are "transactional" you can place it once on a test class.
This works as follows:
If everything is configured correctly spring will open a transaction before the test starts, and will rollback that transaction after the test ends.
The rollback is supposed to clean the inserted data automatically.
Quick googling revealed this tutorial, surely there are many others
#JpaDataTest annotating the test class will rollback every test by default
https://www.baeldung.com/spring-jpa-test-in-memory-database

Spring Junit Database rollback at the end of test class

I have a lot of existing Spring JUnit tests.
All this tests extends an abstract test class.
public class ServiceTest extends AbstractServiceTest {
But in this abstract class we reinitialize the database. So it will reinitialize the database on each test class
#Before
#Override
public void initGlobal() throws Exception {
initDatabase();
... }
I am asking how I can do a rollback on a test class in the end of execution of its tests ? So i can initialize a database one time and rollback changes in every test class
I think having two profiles is a better option one for testing and other one for development and on the testing profile use memory based database like H2 (here is a good example) and on your development profile use your main database
When running the tests use the testing profile.Instead of doing rolling back or deleting data each time you run your test
If you want to use a real database in unit tests that I totally discourage it.You can use the spring test runner to annotate your class and rollback transactions
#RunWith(SpringJUnit4ClassRunner.class)
#TransactionConfiguration(defaultRollback=true)
public class YourTestClass {
#Test
#Transactional
public void myTestMethod() {
// db will get rolled back at the end of the test
}
}

Categories

Resources