Reputation: 1693
I have this problem that is causing me not to use JdbcTestUtils
when running tests with a Transactional context.
If I wrap my test with @Transactional
annotation and use a JdbcTemplate/DataSource
it looks like the transactions used in the production code and by the JdbcTestUtils are not the same, so if I query the db in the then
section the assertion fails.
This is a pseudo test:
@SpringBootTest
class TestClass {
@Autowired
private WebApplicationContext context;
@BeforeEach
void setup() {
RestAssuredMockMvc.webAppContextSetup(context, springSecurity());
}
@Test
@Transactional
@DisplayName("test1")
void test1(@Autowired DataSource dataSource) {
//given
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
assertThat(countRowsInTable(jdbcTemplate, "some_tbl")).isEqualTo(1);
//when
// Execute app code here that adds a record to some_tbl
//then
assertThat(countRowsInTable(jdbcTemplate, "some_tbl")).isEqualTo(2); //<- Assertion fails.
}
As a workaround, I need to use Spring contexted test repositories to retrieve data in the test, but that feels like a bad idea, and I need to maintain these repositories.
Below you will find a simple spring-boot project to show the issue. https://github.com/Marek00Malik/JdbcTestUtils-sample
Upvotes: 1
Views: 1152
Reputation: 12041
I cloned your project and could verify the failing test on my machine.
The reason for your test failure within the assertion of the test failingTestStoringNewObject
:
assertThat(countRowsInTable(jdbcTemplate, "sample_table")).isEqualTo(1);
is that the EntityManger
of JPA (the Spring Data JPA repositories use this under the hood) follows a write-behind strategy and keeps changes to JPA entities in its first-level cache (in-memory). Flushing the changes to the database does not always happen when your code executes .save()
or any other database operation using the EntityManager
.
The EntityManger
tries to postpone flushing and therefore synchronizing its first-level cache with the database as late as possible. There is a lot to read about this and I can recommend Vlad Mihalcea's guide on this.
In your case, as you annotate your test with @Transactional
, your changes will be rolled back after the test and therefore never committed to the database. You can see this in the log of the test:
2020-07-20 09:59:57.754 INFO 12648 --- [ Test worker] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test: [DefaultTestContext@2b8ade2d testClass = SimpleObjectResourceHttpTest, testInstance = pl.code.house.example.jdbc.template.jdbctemplatetest.SimpleObjectResourceHttpTest@
For more visualization on this, you can also enable SQL logging for your tests with
spring:
jpa:
show-sql: on
You'll see that there is actually no INSERT
statement executed, but a call to fetch the primary key of your entity.
If you temporarily use repository.saveAndFlush(newObj).toDto();
inside your SimpleObjectFacade
, you'll see that it works.
I would use the SimpleObjectRepository
for your integration test and use the .count()
method of it. In this case, the underlying EntityManager
recognizes a call to the same table and will flush its current changes before that and you'll get your expected result. Because if you use the JdbcTemplate
there is no interaction with the EntityManager
and therefore the EntityManager
does not flush it changes as you go directly to the database with a SELECT COUNT(0) ...
.
UPDATE: If you don't want to make any adjustments and still use the JdbcTemplate
in conjunction with Hibernate and the JpaRepositories
, you could set the following flush mode for your tests only:
spring.jpa.properties.org.hibernate.flushMode=ALWAYS
This ensures to always flush the persistence context
Upvotes: 1