spring-projects / spring-boot

Spring Boot
https://spring.io/projects/spring-boot
Apache License 2.0
74.94k stars 40.65k forks source link

Spring Boot's @Container annotation breaks testcontainers singleton pattern #36513

Closed magnus-larsson closed 1 year ago

magnus-larsson commented 1 year ago

I have put together a minimal example that demonstrates how the use of @Container with Spring Boot 3.1.2 breaks testcontainers singleton pattern described here: https://java.testcontainers.org/test_framework_integration/manual_lifecycle_control/#singleton-containers.

The example contains two test classes that share a common base class that configure a singleton test container for MySQL. When running tests, it is expected that only one MySQL test container is created.

To reproduce the problem, run the following commands:

git clone https://github.com/magnus-larsson/sb312-tc-singleton-bug.git
cd sb312-tc-singleton-bug
./gradlew test | grep "Creating container for image: mysql:8.0.32"

Expect the following output demonstrating that two MySQL test containers (incorrectly) were created:

2023-07-24T08:55:36.607+02:00  INFO   --- [    Test worker] tc.mysql:8.0.32                          : Creating container for image: mysql:8.0.32
2023-07-24T08:55:46.547+02:00  INFO 73329 --- [    Test worker] tc.mysql:8.0.32                          : Creating container for image: mysql:8.0.32
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended

Edit src/test/java/se/magnus/microservices/core/review/MySqlTestBase.java and comment out the @Container annotation.

Rerunning the tests demonstrates that only one MySQL test container was created:

./gradlew test | grep "Creating container for image: mysql:8.0.32"

Expected output:

2023-07-24T08:57:37.476+02:00  INFO   --- [    Test worker] tc.mysql:8.0.32                          : Creating container for image: mysql:8.0.32
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
mhalbritter commented 1 year ago

You have annotated your base class with @Testcontainers, which is not the case in the Testcontainers documentation.

When you define your base class like this:

public abstract class MySqlTestBase {

  // Extend startup timeout since a MySQLContainer with MySQL 8 starts very slow on Win10/WSL2
  static final JdbcDatabaseContainer database;

  static {
    database = new MySQLContainer("mysql:8.0.32").withStartupTimeoutSeconds(300);
    database.start();
  }

  @DynamicPropertySource
  static void databaseProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.datasource.url", database::getJdbcUrl);
    registry.add("spring.datasource.username", database::getUsername);
    registry.add("spring.datasource.password", database::getPassword);
  }

}

only one container is created and is reused for all tests.

mhalbritter commented 1 year ago

Btw, this works with @ServiceConnection, too:

public abstract class MySqlTestBase {

  // Extend startup timeout since a MySQLContainer with MySQL 8 starts very slow on Win10/WSL2
  @ServiceConnection
  static final JdbcDatabaseContainer database;

  static {
    database = new MySQLContainer("mysql:8.0.32").withStartupTimeoutSeconds(300);
    database.start();
  }

}
magnus-larsson commented 1 year ago

Thanks for pointing it out!

When I applied the singleton pattern, I forgot to remove the @Testcontainers annotation a long time ago. Hasn't been a problem until now :-)

Your suggestion works like a charm!

mhalbritter commented 1 year ago

Thanks for letting us know!