pgiecek
pgiecek

Reputation: 8200

Spring Boot integration test fails if @Transactional

I have a simple Spring Boot application using Spring Data JDBC repositories and exposing REST API. Everything seems to be running OK except for my integration tests. See below an excerpt from repository configuration.

@Configuration
@EnableJdbcRepositories
class RepositoryConfig : AbstractJdbcConfiguration() {

    @Bean
    @Profile("int-test")
    fun intTestDataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .addScript("sql/h2/schema_create.sql")
                .build()
    }

    @Bean
    fun namedParameterJdbcOperations(dataSource: DataSource): NamedParameterJdbcOperations {
        return NamedParameterJdbcTemplate(dataSource)
    }

    @Bean
    fun transactionManager(dataSource: DataSource): TransactionManager {
        return DataSourceTransactionManager(dataSource)
    }
}

The integration test follows.

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@ActiveProfiles("int-test")
@Transactional
class OrganizationControllerIntTest {

    @Autowired
    private lateinit var organizationRepository: OrganizationRepository

    @Autowired
    private lateinit var restTemplate: TestRestTemplate

    @Autowired
    private lateinit var testDataGenerator: TestDataGenerator

    @BeforeEach
    fun beforeEach() {
        val organizations = testDataGenerator.organizations(10).toList()
        organizationRepository.saveAll(organizations)
    }

    @Test
    fun `When GET organizations then return all organizations`() {
        val result = restTemplate.getForEntity("/organizations", String::class.java)
        assertThat(result?.statusCode, equalTo(HttpStatus.OK))
        assertThat(result?.body, containsString("items"))
    }
}

The test is successful if @Transactional annotation is not applied. Since I need the changes to be rollbacked after each test, I would like to make the tests transactional. The problem is that in that case the response does not contain any items like the database would be empty and I cannot figure out why.

Upvotes: 2

Views: 5797

Answers (1)

M. Deinum
M. Deinum

Reputation: 124441

Using @Transactional with a system test won't work.

What happens is the data in your @BeforeEach method is inserted into the database but the transaction never commits. The data is only visible inside the same transaction.

Now you send a HTTP request to the server which opens a new connection to the database and starts a new transaction. However as the data from the other transaction isn't committed it won't see anything.

The @Transactionl will only work when using MockMvc as that will run inside the same transaction and thread. With an actual HTTP request being made that isn't the case.

To use data setup for each test, although you should be cautious with this when doing a full system test! you either:

  1. Write your test in such a way that it doesn't matter, if you really want a system test.
  2. use MockMvc to run your test, instead of an actual HTTP request which will make it (re)use the same transaction.
  3. Don't write a system test but use @WebMvcTest to test your controller

Upvotes: 6

Related Questions