#Transactional annotation in Spring Test - java

I am reading Spring docs about Spring test in: here
And about using #Transactinoal on tests it says:
If your test is #Transactional, it rolls back the transaction at the end of each test method by default. However, as using this arrangement with either RANDOM_PORT or DEFINED_PORT implicitly provides a real servlet environment, the HTTP client and server run in separate threads and, thus, in separate transactions. Any transaction initiated on the server does not roll back in this case.
I do not understand what does it exactly means by Any transaction initiated on the server does not roll back in this case.
Any help is appreciated.

It means that your server won't rollback your changes because it will run in another environment than the test environment.
Only changes you have made in your test environment would be rollbacked.
For instance:
#Autowired
private AnyRepository anyRepository;
#Test
#Transactional
void testSave(){
anyRepository.save(new AnyEntity());
// Will create an entity from your test environment
}
#Test
#Transactional
void testRead(){
anyRepository.findAll();
// Won't find any entities since they were rollbacked
}
On the contrary, if you launched a local instance of Spring using #SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT) for instance), it is detached from your unit test environment, hence:
#Autowired
MockMvc mvc;
#Test
#Transactional
void testSave(){
mvc.perform(post(/* enough to create an entity */);
// Your server, detached from test environment, persists the entity
}
#Test
#Transactional
void testRead(){
mvc.perform(get(/* enough to get that entity */);
// Will get previously created entity (if testSave was run before)
}
If you want to rollback after sending a web request, you could use the #DirtiesContext annotation to reset your context, or check Reset database after each test on Spring without using DirtiesContext.
edit: following comments on original post, it was not clear whether you needed to use WebEnvironment.RANDOM_PORT or if it was a simple question.
Most likely, if you do not need WebEnvironment.RANDOM_PORT, you can simply use WebEnvironment.MOCK, which runs in the same environment that the JUnit tests, hence would actually rollback.

Related

SpringBoot, JPA and Hibernate Unit-Test for Entities layer

I'm implementing a project using SpringBoot, JPA and Hibernate.
I implemented the DB entities layer with JPA repository.
I'm interested to understand the best practice to write unit-tests for this layer.
Point number one: for this layer, from your point of view, it's necessary to use an integrated DB or it' necessary to mock using, for example, Mockito?
My idea, for this layer, it's, for example, to test the entity structure: check fields validation for example, insert and retrieve some data. In this way, I think I could cover the tests for this entire data-layer.
I'm trying to understand these best practices and, in the mean time, I tried to write a first example of the test:
#ActiveProfiles("test")
#DisplayName("Test Item JPA Entity")
#DataJpaTest
#AutoConfigureTestDatabase( replace = AutoConfigureTestDatabase.Replace.NONE )
public class ItemEntityTest {
#Autowired
MyEntityRepository repo;
#Test
#Transactional
public void testEntityCreation() {
Entity e = new Entity();
e.setMyField1("A");
e.setMyField1("A");
//e.setMandatoryField("C")
repo.save(e);
}
}
Unfortunately, In this case, I notiest that the fields validation is not applied (#NotNull or #NotEmpty, or #Column(nullable=false), etc ... If I try to save the entity into my application the validation works fine... the exceptions are raised). Why?
Also some "automatic fields" (for example creation time and last modification time) are not filled.
Is this the correct path? Ho to test my entities definition?
As mentioned in that answer, the problem is that with #DataJpaTest spring will use TestEntityManager and the transactional annotation will override the default auto commit and rollback behaviour by spring boot.
So your test method will pass and assuming the hibernate is the ORM being used here, when the flushing will take place finally, hibernate's pre-insert event will fire and validations will be applied, but your test case will pass till then so it will produce false positive (as the terms used in spring docs)
Solution: You would need to inject entity manager in your test and flush manually it so that hibernate pre-insert event triggers before your test completes.
#ActiveProfiles("test")
#DisplayName("Test Item JPA Entity")
#DataJpaTest
#AutoConfigureTestDatabase( replace = AutoConfigureTestDatabase.Replace.NONE )
public class ItemEntityTest {
#Autowired
MyEntityRepository repo;
#Autowired
private TestEntityManager em;
#Test
#Transactional
public void testEntityCreation() {
Entity e = new Entity();
e.setMyField1("A");
e.setMyField1("A");
//e.setMandatoryField("C")
repo.save(e);
em.flush();
}
}
This must trigger your validations on the entity applied, this is documented in spring framework docs.
Please notice that the documentation is about spring framework and uses session factory, but the concept is same
You may check the spring boot docs as well, which points to the spring framework docs for this behaviour.

#BeforeAll JUnit/spring-boot-test alternative that runs when application context starts

I'm writing a #Repository/#Service integration test that leverages an embedded database. In my test class, I would like to preload my database with some data.
I'm currently using #BeforeEach to load in my sample data, however, this code is run upon each test in my class.
Is there any way that I can load in my test data after Spring application context has loaded, but before any test has been run?
My current approach:
#BeforeEach
public void before() {
repository.save(...); // -> prepopulates repository with sample data
}
#Test
public void testService() {
service.get(...); // -> gathers existing record
}
#Test
public void deleteById() {
service.delete(...); // -> deletes existing record
}
However... with this, I am required to flush out the records after every test. Otherwise any unique constraints can easily be violated.
Rather than using #BeforeEach which is required to run before every test... is it possible to load this in in a #BeforeAll kind of fashion that happens after the spring application context has been loaded?
Is there any way that I can load in my test data after Spring application context has loaded
Basically yes, I think you can do that:
The idea is to load the SQL data when the application context is started or in the process of being started.
For example, spring boot integration with Flyway works this way (the bean of Flyway is created and loaded). So, in theory, you could merely use Flyway with test migrations that will contain all the relevant SQL scripts of test data generation.
How can you do this technically?
Here is one way:
Create a special bean (just like the way it works with Flyway) that would depend on your repository and in post construct save the data:
#Component
public class SqlGenerationBean {
#Autowired
private MyRepository repo;
#PostConstruct
public void init() {
repo.save();
}
}
Another way of doing is to create a listener that will be called upon the application context started and again will call the same repo.save().
In both cases the bean/listener code should not be accessible from production (it's only for tests): so put it somewhere under src/test/java for example
Now once the application context is started you can use a neat trick:
Mark your tests with #Transactional annotation. Spring will wrap the code in an artificial transaction that will be rolled back automatically (even if the test succeeds) so that all the data that you'll modify during the test will be rolled back and basically before each test, you'll have the same state (that is identical to the state of the database when/after the application context starts). Of course, if you use DDL in the test, some databases can't make it a part of transaction but it depends on the database really.
Another interesting point here is that the application context can be cached even between the test cases (created only once), so keep this in mind.
In this case I would just create a constructor for the test class. It will be triggered before everything.
#BeforeEach runs before each tests but after all initialisations .
you can also just use Mockito and mock the result without need to clean and overcomplicate
Just add following snippet to your code. This is just like you can do to detect that Spring application is really started.
#Configuration
public class AppConfig implements ApplicationListener<ApplicationReadyEvent> {
/**
* This is to indicate in the logs when the application has actually started and everything is loaded.
*/
#Override
public void onApplicationEvent(ApplicationReadyEvent event) {
ApplicationContext context = event.getApplicationContext();
Environment env = context.getEnvironment();
// do what you want on application start
}
}
P.S. For database manipulation in test #Sql is the best candidate as was mentioned in comment.

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.

Testing Hibernate DAO using Junit

I am using a combination of Spring and Hibernate in my project and would like to test the DAO methods like Save and Delete methods.
daoFoundation is a wrapper class created over hibernateSession.
#Override
public String createSubject(Subject subject) {
String subjectId = (String) daoFoundation.save(subject);
return subjectId;
}
This is what I wrote in my JUnit Runs with SpringJunit4ClassRunner
I created the subject object in my SetupMethod.
#Test
public void createSubjectTest(){
subjectDao.createSubject(subject);
assertNotNull(hassSubjectSelection.getId());
}
Is this sufficient or do I need to write anything additional in my test class?
The easiest way is to import your Spring application context, autowire in the DAO's you want to test and then mark either your test methods or the entire class as #Transactional. This will create a Hibernate session, run your test and then automatically roll back the transaction so you don't effect your database state with your tests.
Have a look at how to run unit tests with Spring here. You can get Spring to create your entire application context using the #ContextConfiguration annotation. So if you create your database using an XML file called database-servlet.xml then you would annotate
#ContextConfiguration(locations={"classpath:/database-servlet.xml"})
public class Test()
You can use the annotation #RunWith(SpringJUnit4ClassRunner.class) to use functionality of the Spring TestContext Framework with your unit tests. This allows you to do things like declare expected exceptions that should be thrown, run timed tests, repeat test runs X times and a bunch of other cool stuff.
Basically to get this working, your test class should look similar to the following:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={YOUR APP CONTEXT FILES HERE AS A COMMA SEPARATED LIST})
public class Test(){
#Autowired
private YourDAO yourDAO;
#Test
#Transactional
public void testSave(){
//Test save method here. Any database changes made here will be
//automatically rolled back when the test finishes.
}
Let me know if that works or not.
The best way to test your dao layer is to use the spring jdbctemplate to write data to your database the test your get and delete methods. Then in the #after delete the records you wrote. Then use hibernate to write to your database and use jdbctemplate to read them back. Then delete your test rows. Anything less and all you are really doing is testing hibernate's caching.

How to propagate Spring transaction to another thread?

Perhaps, I am doing something wrong, but I can't find a good way out for the following situation.
I would like to unit test a service that uses Spring Batch underneath to execute jobs. The jobs are executed via pre-configured AsyncTaskExecutor in separate threads. In my unit test I would like to:
Create few domain objects and persist them via DAO
Invoke the service method to launch the job
Wait until the job is completed
Use DAO to retrieve domain objects and check their state
Obviously, all above should be executed within one transaction, but unfortunately, transactions are not propagated to new threads (I understand the rationale behind this).
Ideas that came to my mind:
Commit the transaction#1 after step (1). Is not good, as the DB state should be rolled back after the unit test.
Use Isolation.READ_UNCOMMITTED in job configuration. But this requires two different configurations for test and for production.
I think the simplest solution would be configure the JobLauncher with a SyncTaskExecutor during test execution - this way the job is executed in the same thread as the test and shares the transaction.
The task executor configuration can be moved to a separate spring configuration xml file. Have two versions of it - one with SyncTaskExecutor which is used during testing and the other AsyncTaskExecutor that is used for production runs.
Although this is not a true solution to your question, I found it possible to start a new transaction inside a worker thread manually. In some cases this might be sufficient.
Source: Spring programmatic transactions.
Example:
#PersistenceContext
private EntityManager entityManager;
#Autowired
private PlatformTransactionManager txManager;
/* in a worker thread... */
public void run() {
TransactionStatus tx = txManager.getTransaction(new DefaultTransactionDefinition());
try {
entityManager.find(...)
...
entityManager.flush(...)
etc...
txManager.commit(tx);
} catch (RuntimeException e) {
txManager.rollback(tx);
}
}
If you do want separate configurations, I'd recommend templating the isolation policy in your configuration and getting its value out of a property file so that you don't wind up with a divergent set of Spring configs for testing and prod.
But I agree that using the same policy production uses is best. How vast is your fixture data, and how bad would it be to have a setUp() step that blew away and rebuilt your data (maybe from a snapshot, if it's a lot of data) so that you don't have to rely on rollbacks?

Categories

Resources