I have a Spring Boot application with Spring Data Rest and I use @WebIntegrationTest
along with the TestRestTemplate
in my integration tests. The base class for the tests looks something like this:
@RunWith(SpringJUnit4ClassRunner.class)
@ActiveProfiles(profiles = "test")
@SpringApplicationConfiguration(classes = Application.class)
@Transactional
@TransactionConfiguration
@WebIntegrationTest("server.port: 0")
public abstract class IntegrationTest {
...
}
I was testing the creation of an entity by using the TestRestTemplate
to perform a POST
request to a resource. The problem is that the transaction that persists the entity on the database is not rolled back even thought my tests are configured to be transactional, so the entity remains on the database after the test. I kind of understand that because the transaction being rolled back in the test is not the same one persisting the entity.
Now my question is, is there any way of rolling back the transactions triggered by the requests made through the RestTemplate
in a test method?
No. It is not possible to roll back the transactions managed by your deployed application.
When you annotate your test class with
@WebIntegrationTest
and@SpringApplicationConfiguration
, Spring Boot will launch an embedded Servlet container and deploy your application in it. So in that sense, your test and application are running in two different processes.The Spring TestContext Framework only manages Test-managed transactions. Thus, the presence of
@Transactional
on your test class only influences local test-managed transactions, not those in a different process.As someone else already mentioned, a work-around would be to reset the state of the database once your test has completed. For this you have several options. Consult the Executing SQL scripts section of the reference manual for details.
Regards,
Sam (author of the Spring TestContext Framework)
Instead of using
TestRestTemplate
orRestTemplate
, you could use MockMvc. In that case,@Transactional
will roll back the data changes.If you're testing the logic in your
@RestController
for the POST operation, then MockMvc should suffice. If your test involves filters, then you likely need to useTestRestTemplate
. In that case, as others pointed out, you'll need to reset your test data in the database at the end of the tests.I recently needed this because our 1300+ integration tests took +-70 minutes and it is doable but through some low-level plumbing.
As the memory of the testcase and boot server is shared, I got it working through writing a DataSource implementation which manages only 1 connection and returns that to any caller of getConnection(). The connection itself is a Connection implementation which acts as a wrapper to the real connection.
The datasource is then added to the @SpringBootTest configuration.
I won't post the code here (company property), but here are the major details:
DataSource maintains the Connection wrapper as a static field.
Upon dataSource.getConnection(), a counter for all "borrowers" gets incremented.
Upon connection.close(), this counter gets decremented. If it becomes 0, it means the testcase is over and the transaction can get rolled back.
There is 1 gotcha to take into account and that's spring's transaction management: you have to emulate transaction rollbacks.
To do this, on every connection.open(), I create a savepoint (connection.setSavePoint() ) and push it in a LinkedList. Ony any connection.rollback(), I pop it from the stack and perform a connection.rollback(savePoint) .
I also had to hack in a possibility to enable commit functionality, as the db must be created in the @BeforeAll().
I don't rely on @Transactional in my test case, but call dataSource.getConnection / connection.close() in the @Before / @After methods of the abstract parent to mark the transaction boundaries.
It's not the prettiest solution, but this reduced the test time to from 70 to 30 minutes (while keeping the tests green ;) ), which is a nice win.
Hope this helps someone.