I want to be able to simply do
@MyTestConfig
class MyTests {
@Autowired SomeJpaRepo repo;
@Test
void findAll() {
assertThatCode(repo::findAll).doesNotThrowAnyExceptions();
}
}
What I started was to do the annotation
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@TestContainers
public @interface MyTestConfig {
}
With that the following test works give
@MyTestConfig
class MyTests {
@Container
private static final MySQLContainer<?> MYSQL_CONTAINER =
new MySQLContainer<>(
DockerImageName.parse("library/mariadb")
.asCompatibleSubstituteFor("mysql"));
@DynamicPropertySource
static void mysqlProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", MYSQL_CONTAINER::getJdbcUrl);
registry.add("spring.datasource.username", MYSQL_CONTAINER::getUsername);
registry.add("spring.datasource.password", MYSQL_CONTAINER::getPassword);
}
@Autowired SomeJpaRepo repo;
@Test
void findAll() {
assertThatCode(repo::findAll).doesNotThrowAnyExceptions();
}
}
However, I am unable to get that TestContainer configuration out to a separate file that is managed by @MyTestConfig
, the closest thing that worked which I wanted to avoid if possible is creating an abstract test class
abstract class AbstractMyTest {
@Container
private static final MySQLContainer<?> MYSQL_CONTAINER =
new MySQLContainer<>(
DockerImageName.parse("library/mariadb")
.asCompatibleSubstituteFor("mysql"));
@DynamicPropertySource
static void mysqlProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", MYSQL_CONTAINER::getJdbcUrl);
registry.add("spring.datasource.username", MYSQL_CONTAINER::getUsername);
registry.add("spring.datasource.password", MYSQL_CONTAINER::getPassword);
}
}
I am thinking that it may be something along the lines of creating a JUnit extension class rather than utilizing Spring annotations.
Just a note I'm still on Spring Boot 2.7.18, so I don't have the @ServiceContainer
annotation.
To run a test container outside of the test class without using an abstract test class, there are several approaches:
@TestConfiguration
using @ServiceConnection annotation (only for >=3.1 spring-boot)One of the disadvantages of this approach is that you cannot use @Testcontainers
, @Container
or @DynamicPropertySource
annotations and have to start container manually:
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;
public class MySqlTestContainerExtension implements BeforeAllCallback {
private static final MySQLContainer<?> MYSQL_CONTAINER =
new MySQLContainer<>(
DockerImageName.parse("library/mariadb")
.asCompatibleSubstituteFor("mysql"));
@Override
public void beforeAll(ExtensionContext context) {
if (!MYSQL_CONTAINER.isRunning()) {
MYSQL_CONTAINER.start();
System.setProperty("spring.datasource.url", MYSQL_CONTAINER.getJdbcUrl());
System.setProperty("spring.datasource.username", MYSQL_CONTAINER.getUsername());
System.setProperty("spring.datasource.password", MYSQL_CONTAINER.getPassword());
}
}
}
and after that you have to add this extension like any other extension using @ExtendWith
above your test class or custom annotation:
import org.junit.jupiter.api.extension.ExtendWith;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ExtendWith(MySqlTestContainerExtension.class) // custom extension
public @interface MyTestConfig {
}
As stated in the documentation, this approach eliminates the need to define a container in Java code. The only requirement is to set the correct JDBC URL in the test properties by adding tc:
to the original URL, like:
spring:
datasource:
username: username
password: password
url: jdbc:tc:mysql:latest:///databasename
jpa:
database-platform: org.hibernate.dialect.MySQL8Dialect
However, if we want to use some other image name, like library/mariadb
as compatible substitute for mysql
, we have to:
ImageNameSubstitutor
package com.example.somepackage;
import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.ImageNameSubstitutor;
public class ExampleImageNameSubstitutor extends ImageNameSubstitutor {
private final DockerImageName imageName = DockerImageName.parse("library/mariadb")
.asCompatibleSubstituteFor("mysql");
@Override
public DockerImageName apply(DockerImageName original) {
if (original.asCanonicalNameString().contains("mysql")) {
return imageName;
}
return original;
}
@Override
protected String getDescription() {
return "example image name substitutor";
}
}
resources/testcontainers.properties
image.substitutor=com.example.somepackage.ExampleImageNameSubstitutor
If we use spring-boot 3.1 or higher, we can define a bean of testcontainer and mark it with @ServiceConnection
annotation:
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;
@TestConfiguration(proxyBeanMethods = false)
public class MySqlContainerConfiguration {
@Bean
@ServiceConnection
public MySQLContainer<?> mySqlContainer() {
DockerImageName imageName = DockerImageName.parse("library/mariadb").asCompatibleSubstituteFor("mysql");
return new MySQLContainer<>(imageName);
}
}
Summary:
As we found out, the test configuration approach is not suitable for you, since you are using version 2.7.18, so there are two options left - JUnit extension and Database containers launched via JDBC URL scheme.
Personally I would choose the Junit extension, since the configuration of the test container is more visible and no need to write a custom ImageNameSubstitutor
, however in some cases, the option of launching container via the JDBC URL looks even more elegant, reducing the container creation boilerplate code.