'Environment variables set via System#setProperty used in Spring application.yml
I have been reading Baeldung's Testcontainer HowTo where in section 4 it is mentioned:
In the start() method we use
System#setPropertyto set connection parameters as environment variables. We can now put them in our application.properties file:spring.datasource.url=${DB_URL} spring.datasource.username=${DB_USERNAME} spring.datasource.password=${DB_PASSWORD}
I tried this approach - albeit with using Junit5 and hence no @ClassRule to fire up the container as stated in the tutorial, yet @Container instead.
It turns out: These environment variables are seemingly not used in the Spring context. They seems to be empty, yet e.g. the DB URL apparently defaults to localhost:5432 when using PostgreSQL.
My question:
I wonder if and how programatically defined environment variables would work in an application.properties or application.yml.
Would not @SpringBootTest load my context as defined in application.yml (with empty env vars) before the Container is initialized and defines those DB_ variables?
It would be nice if this approach actually worked, because I believe this would help me to trigger Liquibase prior to test execution, although there is certainly a way to do that programmatically as well.
PS: With a custom ApplicationContextInitializer which programatically sets the data source properties, the test does work.
**** EDIT ****
I found out that the feature of setting properties via programmatically defined env vars does work. However, I was misled due to the following setup:
My project base directory has a subdir config with config/application.yml as the externalized config source in it. I thought that is a good idea - better than placing a config in src/main/resources, because - unless one explicitly excludes it - it would land in the jar and I would not necessarily want that.
However, I did believe that a src/test/resources/application.yml (further called: T) would override any settings obtained from config/application.yml (further called: P). Apparently not quite, which I found out after adding Liquibase:
P contains spring.liquibase.enable=false while T contains spring.liquibase.enable=true and spring.liquibase.changeLog=classpath:/db/foo.yaml. Launching the tests, I see no Liquibase updates. Setting spring.liquibase.enable=true in P, however, results in Liquibase updates being applied during integration tests.
Also, not setting spring.liquibase.changeLog=classpath:/db/foo.yaml in T makes Liquibase complain about a missing changelog file (it defaults to some file name I do not use). That means, T must be read after all. But apparently, properties set in P are not overridden in T (which may be a feature I was not aware of).
Such overriding does work, however, when moving P to src/main/resources/application.yml.
Also removed the Junit4 annotation @RunWith following @M.Deinum's good remark.
**** END OF EDIT ****
Full test class:
// @RunWith(SpringRunner.class)
@ExtendWith(SpringExtension.class)
@SpringBootTest
@Testcontainers
@ActiveProfiles("integration-test-postgres")
class MyRepositoryIntegrationTest {
@Autowired
MyRepository repository;
@Container
static JdbcDatabaseContainer<?> databaseContainer = IntegrationTestPostgresContainer.getInstance();
@Test
void test() {
assertEquals("postgres", System.getProperty("DB_USERNAME"));
assertEquals("postgres", System.getProperty("DB_PASSWORD"));
assertTrue(databaseContainer.isRunning());
}
}
Container extension:
public class IntegrationTestPostgresContainer extends PostgreSQLContainer<IntegrationTestPostgresContainer> {
private static final String IMAGE_VERSION = "postgres:12.3";
private static IntegrationTestPostgresContainer container;
private IntegrationTestPostgresContainer() {
super(IMAGE_VERSION);
}
public static IntegrationTestPostgresContainer getInstance() {
if (container == null) {
container = new IntegrationTestPostgresContainer()
.withDatabaseName("mddb")
.withUsername("postgres")
.withPassword("postgres");
}
return container;
}
@Override
public void start() {
super.start();
System.setProperty("DB_URL", container.getJdbcUrl());
System.setProperty("DB_USERNAME", container.getUsername());
System.setProperty("DB_PASSWORD", container.getPassword());
}
@Override
public void stop() {
// noop
}
}
Initializer, to be added to the test context via @ContextConfiguration(initializers = {DataSourceContextInitializer.class}) in order to make the test pass:
public class DataSourceContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private final static JdbcDatabaseContainer<?> CONTAINER = IntegrationTestPostgresContainer.getInstance();
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues.of(
"spring.datasource.url=" + CONTAINER.getJdbcUrl(),
"spring.datasource.username=" + CONTAINER.getUsername(),
"spring.datasource.password=" + CONTAINER.getPassword()
)
.applyTo(configurableApplicationContext.getEnvironment());
}
}
application.yml
---
spring:
config:
activate:
on-profile: integration-test-postgres
datasource:
driver-class-name: org.postgresql.Driver
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
