top of page

Spring integration testing II: automatic rollback transaction model

  • Cyril Sahula
  • Feb 18, 2023
  • 3 min read

Updated: Dec 14, 2023




The article belongs to a series about integration testing. You can check the kick-off article.


Let’s imagine an application that provides a CRUD REST API. We would like to implement an integration test that tests the whole functionality with a relational database. What we must avoid is that a test case would influence other future test cases by saving data into DB. In this article, I would like to solve this problem with a strategy that can be called the automatic rollback transaction, and it is useful just for transactional DBs. It is the most used model in many projects, yet I would say the most unfit one for most advanced applications. There is a better way. I will explain it in the next articles. It is the default configuration when a developer uses the spring-integration library.

The principle of the strategy is simple. Each test case starts its own transaction which isautomatically rollbacked after the test case is finished. In the picture below are two testcases, and after each one, you can see the database rollback.





How to achieve the behavior from the picture above? Look at the example test class. The annotation @Transactional guarantees that each test starts its own transaction. But how is automatic rollback done? The answer is the Spring magic :) and you can study the documentation for more understanding. The framework automatically registers a TransactionalTestExecutionListener, which rollbacks the transactions in the tested application. The rollback works, but there are significant downsides. So let’s jump to the darkside of it.


Dark side (limits)


The same thread

The first show-stopper is that a test case must work in the same thread as a tested application to be able to handle DB rollback. Look at the test case whenHttpClientWorksInSameThreadThenDataCreatedInTestAreVisible. The MockMvc client runs in the same thread as the tested code, and therefore data prepared in the setup method is visible for the application and the test can assert them. Although the second test case whenHttpClientWorksInDifferentThreadThenDataCreatedInTestAreNotReachable uses WebClient which is a real HTTP client, and therefore the application uses a different thread and has no clue about the customer John Travolta created in the setup method. Of course, this concrete code has a solution because WebClient has also WebTestClient implementation, but I just demonstrate the problem which you would encounter, for example with messaging API, testing Apache Camel routes, or any other advanced APIs (messaging, etc.) or frameworks. For such advanced cases, the automatic rollback approach can not be used; therefore, an application runs a different thread than the test case, and TransactionalTestExecutionListener can not do its job and rollback data after a test case.


The same transaction

The second show-stopper for the strategy is more advanced transaction management in an application. Most simple projects have only one transaction per API call, although in more complicated programs the API call can process more DB transactions. In the Spring framework, we can define @Transactional annotation with the propagation REQUIRES_NEW, which starts a completely new transaction even if there is already one open. The TransactionalTestExecutionListener can not rollback a transaction that is independently created by an application, therefore data is committed to DB. Also, such transaction management can come with a framework or parallel processing.

Check the test case which tests the CRUD API. The test case demonstrates that data from the API method createCustomer are not rollbacked. We do not want to limit our solution because of testing, and therefore this testing strategy does not fit more advanced solutions with advanced transaction management.


Conclusion

The article presented the most simple model which can be used for a really basic projectwith only transactional DBs. Let’s summarize the pros and cons:


Pros

  • simple to configure

  • possible run against DB with data

Cons

  • just for transactional DBs

  • can not be used in an application with advanced transaction management

  • can not be used in an application with some APIs

My conclusion is that we can use this approach for simple services, or at the beginning of a project when we are not sure of the final design, DBs, etc., or if we are in a hurry but we mustbe aware of the limits.

Comentarios


Ya no es posible comentar esta entrada. Contacta al propietario del sitio para obtener más información.
bottom of page