Follow-up of this limitation reported on SO, it would be nice if users were able to configure the URL that @AutoConfigureTestDatabase uses. Right now if they want a specific dialect or any other extra parameter, they have to switch off our support to configure things manually.
Comment From: snicoll
We've spent some time discussing this feature and we're willing to investigate a support of testcontainers rather which would provide all the necessary flexibility.
Comment From: snicoll
Now that we have r2dbc support, we should also take that use case into account so that the ConectionFactory points to a database replaced via this mechanism.
Comment From: wilkinsona
I've been experimenting a little bit in this area. I have a test class like this:
@JdbcTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
@Testcontainers(disabledWithoutDocker = true)
@Import(SomeRepository.class)
@Sql(scripts = "data.sql")
class SomeRepositoryTests {
@Container
private static final PostgreSQLContainer<?> postgresqlContainer = new PostgreSQLContainer<>("postgres:13.2")
.withDatabaseName("example");
@Autowired
private SomeRepository someRepository;
// @Test methods
I've then got a context customizer factory and a context customizer that turns each JdbcDatabaseContainer field into a DataSource bean:
/**
* A {@link ContextCustomizerFactory} that returns a
* {@link JdbcDatabaseContainerContextCustomizer} when {@link JdbcDatabaseContainer} is on
* the classpath and the test class has one or more static fields of that type.
*
* @author Andy Wilkinson
*/
class JdbcDatabaseContainerContextCustomizerFactory implements ContextCustomizerFactory {
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
if (ClassUtils.isPresent("org.testcontainers.containers.JdbcDatabaseContainer", testClass.getClassLoader())) {
Set<Field> jdbcDatabaseContainerFields = new HashSet<>();
ReflectionUtils.doWithFields(testClass, (field) -> {
if (Modifier.isStatic(field.getModifiers())
&& JdbcDatabaseContainer.class.isAssignableFrom(field.getType())) {
ReflectionUtils.makeAccessible(field);
jdbcDatabaseContainerFields.add(field);
}
});
if (!jdbcDatabaseContainerFields.isEmpty()) {
return new JdbcDatabaseContainerContextCustomizer(jdbcDatabaseContainerFields);
}
}
return null;
}
}
/**
* {@link ContextCustomizer} that registers a {@link DataSource} bean for each
* {@link JdbcDatabaseContainer} field in the test class.
*
* @author Andy Wilkinson
*/
class JdbcDatabaseContainerContextCustomizer implements ContextCustomizer {
private final Set<Field> jdbcDatabaseContainerFields;
JdbcDatabaseContainerContextCustomizer(Set<Field> jdbcDatabaseContainerFields) {
this.jdbcDatabaseContainerFields = jdbcDatabaseContainerFields;
}
@Override
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
for (Field field : this.jdbcDatabaseContainerFields) {
JdbcDatabaseContainer<?> container = (JdbcDatabaseContainer<?>) ReflectionUtils.getField(field, null);
BeanDefinition definition = new RootBeanDefinition(DataSource.class,
() -> DataSourceBuilder.create(context.getClassLoader()).url(container.getJdbcUrl())
.username(container.getUsername()).password(container.getPassword()).build());
((BeanDefinitionRegistry) context.getBeanFactory()).registerBeanDefinition(field.getName(), definition);
}
}
}
I think this isn't too far off. I'd prefer it if @AutoConfigureTestDatabase(replace = Replace.NONE) wasn't necessary. If this context customization was done in Boot itself, we'd also need some way of opting in as it'd be too much for it to always happen. Perhaps a @TestDatabase annotation on the test's postgresqlContainer field?
The above isn't hugely Testcontainers-specific and I wonder if there may be some mileage in some general support for adding beans to the context or setting properties in the environment that are derived from static fields in a test class. It could be useful for https://github.com/spring-projects/spring-boot/issues/27151 as well, for example, with a mechanism that allows the JdbcDatabaseContainer to DataSource bean or org.neo4j.harness.Neo4j to spring.neo4j.* properties capability to be contributed.
Comment From: vpavic
This was perhaps made obsolete by https://github.com/spring-projects/spring-boot/issues/35253?
Comment From: wilkinsona
Yes, I think it was. Thanks, @vpavic.