embedded-database-spring-test icon indicating copy to clipboard operation
embedded-database-spring-test copied to clipboard

IllegalStateException when running tests with Flyway

Open ii02735 opened this issue 11 months ago • 4 comments

Hello,

Thank you a lot for your work.

I want to use the embedded PostgreSQL database along with flyway migrations, on a SpringBoot application. When I run this test :

@SpringBootTest
@ExtendWith(FlywayTestExtension.class)
@AutoConfigureEmbeddedDatabase(beanName = "libraryDataSource")
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, OptimizedFlywayTestExecutionListener.class })
@FlywayTest(flywayName = "libraryFlyway")
class InsertingBookTest {

    @Autowired
    private BookRepository bookRepository;

    @Test
    void testGetFirstBook() {
        assertThat(bookRepository.findFirstByName("my book")).isNotNull();
    }
}

It fails with the following exception :

java.lang.IllegalStateException: Using org.flywaydb.test.FlywayTestExecutionListener and org.flywaydb.test.junit.FlywayTestExecutionListener is forbidden, use io.zonky.test.db.flyway.OptimizedFlywayTestExecutionListener instead

	at io.zonky.test.db.flyway.FlywayDatabaseExtension$FlywayDatabaseExtensionInterceptor.apply(FlywayDatabaseExtension.java:186)
	at io.zonky.test.db.flyway.FlywayDatabaseExtension$FlywayDatabaseExtensionInterceptor.invoke(FlywayDatabaseExtension.java:163)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728)
	at org.flywaydb.core.Flyway$$SpringCGLIB$$0.clean(<generated>)
	at org.flywaydb.test.FlywayTestExecutionListener.dbResetWithAnnotation(FlywayTestExecutionListener.java:381)
	at org.flywaydb.test.FlywayTestExecutionListener.handleFlywayTestAnnotationForClass(FlywayTestExecutionListener.java:181)
	at org.flywaydb.test.FlywayTestExecutionListener.beforeTestClass(FlywayTestExecutionListener.java:160)

Here is my dependencies :

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.flywaydb</groupId>
			<artifactId>flyway-core</artifactId>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.flywaydb.flyway-test-extensions</groupId>
			<artifactId>flyway-spring-test</artifactId>
			<version>10.0.0</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>io.zonky.test</groupId>
			<artifactId>embedded-postgres</artifactId>
			<version>1.2.10</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>io.zonky.test</groupId>
			<artifactId>embedded-database-spring-test</artifactId>
			<version>2.6.0</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

For the sake of bug reproducing, you can use my repository : https://github.com/ii02735/spring-boot-flywaytest-multiple-datasources

Thank you a lot for your help.

ii02735 avatar May 02 '25 20:05 ii02735

Hi, thanks for your question and the reproducer.

The problem is in using the @ExtendWith(FlywayTestExtension.class) annotation, which activates the org.flywaydb.test.junit5.FlywayTestExtension class, which causes the exception you described.

Try using the @ExtendWith(SpringExtension.class) annotation instead. This should solve the problem.

tomix26 avatar May 03 '25 10:05 tomix26

@tomix26 Hi, thank you for your quick reply.

I did what you told me, I replaced @ExtendWith(FlywayTestExtension.class) with @ExtendWith(SpringExtension.class) :

@SpringBootTest
@ExtendWith(SpringExtension.class)
@AutoConfigureEmbeddedDatabase(beanName = "libraryDataSource")
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, OptimizedFlywayTestExecutionListener.class })
@FlywayTest(flywayName = "libraryFlyway")
class InsertingBookTest {

But It didn't work, this time with another error (my migrations are not loaded) :

2025-05-03T13:36:15.796+02:00  INFO 33746 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2025-05-03T13:36:15.796+02:00  INFO 33746 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2025-05-03T13:36:16.216+02:00  INFO 33746 --- [           main] org.ii02735.InsertingBookTest            : Started InsertingBookTest in 5.037 seconds (process running for 6.208)
2025-05-03T13:36:16.219+02:00  INFO 33746 --- [           main] o.f.test.FlywayTestExecutionListener     : ---> Start reset database for  'org.ii02735.InsertingBookTest'.
2025-05-03T13:36:16.234+02:00  INFO 33746 --- [           main] o.f.test.FlywayTestExecutionListener     : <--- Finished reset database  for  'org.ii02735.InsertingBookTest'.
2025-05-03T13:36:16.414+02:00  INFO 33746 --- [gres:pid(33837)] i.z.t.d.p.embedded.EmbeddedPostgres      : 2025-05-03 13:36:16.414 CEST [33857] ERROR:  relation "book" does not exist at character 31
2025-05-03T13:36:16.414+02:00  INFO 33746 --- [gres:pid(33837)] i.z.t.d.p.embedded.EmbeddedPostgres      : 2025-05-03 13:36:16.414 CEST [33857] STATEMENT:  select b1_0.id,b1_0.name from book b1_0 where b1_0.name=$1 fetch first $2 rows only
2025-05-03T13:36:16.419+02:00  WARN 33746 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 0, SQLState: 42P01
2025-05-03T13:36:16.419+02:00 ERROR 33746 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : ERROR: relation "book" does not exist
  Position : 31

org.springframework.dao.InvalidDataAccessResourceUsageException: JDBC exception executing SQL [select b1_0.id,b1_0.name from book b1_0 where b1_0.name=? fetch first ? rows only] [ERROR: relation "book" does not exist
  Position : 31] [n/a]; SQL [n/a]

	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:281)
	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:256)
	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:241)
	at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:560)
	at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
	at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:343)
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:160)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)

And after digging a bit, The @SpringBootTest annotation already offers @ExtendWith(SpringExtension.class) :

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(SpringBootTestContextBootstrapper.class)
@ExtendWith({SpringExtension.class})
public @interface SpringBootTest {
    @AliasFor("properties")

So I tried to simplify my annotations on my test class :

@SpringBootTest
@AutoConfigureEmbeddedDatabase(beanName = "libraryDataSource")
@FlywayTest(flywayName = "libraryFlyway")
class InsertingBookTest {

    @Autowired
    private BookRepository bookRepository;

    @Test
    void testGetFirstBook() {
        assertThat(bookRepository.findFirstByName("my book")).isNotNull();
    }
}

But I get the same error as above.

ii02735 avatar May 03 '25 11:05 ii02735

Here's a working version: https://github.com/tomix26/spring-boot-flywaytest-multiple-datasources/commit/1b56b0f7f692288c2ea8a215cd159d3891fe7036

The main issue was missing FlywayMigrationInitializer beans, which ensure Flyway migrations are processed during context/application startup. The Zonky library expects this initialization, as from a typical application's perspective, this is the desired behavior. From a testing perspective, where migrations are executed using the @FlywayTest annotation, it's theoretically possible to skip this initial initialization. However, the Zonky library doesn't account for this, which leads to inconsistency with the H2 database. I'll try to address this issue in future versions.

tomix26 avatar May 04 '25 11:05 tomix26

@tomix26 Hello, thank you very much for your detailed explanation and for your solution.

I'm going to stick with it, as it's the closest to what I need.

Thank you again for your work 🙂

ii02735 avatar May 04 '25 18:05 ii02735