Open snicoll opened 4 years ago
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.
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.
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.
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.