11

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?

3 Answers3

26

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)

Sam Brannen
  • 29,611
  • 5
  • 104
  • 136
  • Thanks for the help Sam. I assumed that was the answer. I'm already reseting the database state. Cheers. – Daniel Francisco Sabugal Apr 20 '15 at 09:55
  • One last question. If the test and the application are running in two different processes how is possible to still inject its components into the test class? – Daniel Francisco Sabugal Apr 24 '15 at 15:15
  • 4
    When I said _different process_, that was perhaps a bit misleading. The embedded container and your test run in the same JVM process; however, the embedded Servlet container naturally executes requests in different threads from its own thread pool. Since Spring's transaction support is based on `ThreadLocal`s, the notion of differing threads is what is important regarding transaction boundaries. The `ApplicationContext`, however, is launched in the test's thread. So components from the context can be injected into your test, even if those components are executed in the container's threads. – Sam Brannen Apr 27 '15 at 11:38
0

Instead of using TestRestTemplate or RestTemplate, 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 use TestRestTemplate. In that case, as others pointed out, you'll need to reset your test data in the database at the end of the tests.

Paulo Merson
  • 13,270
  • 8
  • 79
  • 72
0

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.