I'm working on a spring boot (v 2.2.4) app, specifically to add integration tests which leverage Testcontainers to instantiate a docker container that runs a Postgres instance for tests to perform database transactions against. The tests push our database schema into the Postgres instance via Liquibase . I implemented this following this guide . The connection to the test time Postgres is managed by a class called TestPostgresConfig.java (See below). The liquibase operations are performed by a SpringLiquibase object defined in the same class. I run into a problem when I try running the application after successfully building. The issue is the Spring context tries to instantiate the SpringLiquibase bean at runtime (fails due to db.changelog-master.yaml not being found) and I don't want it to do so:
WARN [main] org.springframework.context.support.AbstractApplicationContext: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'liquibase' defined in class path resource
[org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration$LiquibaseConfiguration.class]: Invocation of init method failed; nested exception is liquibase.exception.ChangeLogParseException: Error parsing classpath:db/changelog/changelog-master.yaml
Cause by java.io.FileNotFoundException class path resource [db/changelog/changelog-master.yaml] cannot be resolved to URL because it does not exist
This file does not exist, will never exist in this project, and liquibase should not be trying to push change logs at runtime in the first place. I need help figuring out why Spring tries to load the liquibase bean so I can keep that from happening at runtime.
My set up:
@SpringBootApplication
@EnableRetry
@EnableCommonModule
@EnableScheduling
@Slf4j
@EnableConfigurationProperties({
ExternalProperties.class,
ApplicationProperties.class
})
public class MyApplication implements WebMvcConfigurer, CommandLineRunner {
@Autowired
MyService myService;
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
public void run(String... args) throws Exception {
myService.doSomething();
}
}
TestPostgresConfig.java:
@TestConfiguration
@Profile("integration")
public class TestPostgresConfig {
@Bean
public DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("org.postgresql.Driver");
ds.setUrl(format("jdbc:postgresql://%s:%s/%s", MyIT.psqlContainer.getContainerIpAddress(),
MyIT.psqlContainer.getMappedPort(5432), MyIT.psqlContainer.getDatabaseName()));
ds.setUsername(MyIT.psqlContainer.getUsername());
ds.setPassword(MyIT.psqlContainer.getPassword());
ds.setSchema(MyIT.psqlContainer.getDatabaseName());
return ds;
}
@Bean
public SpringLiquibase springLiquibase(DataSource dataSource) throws SQLException {
tryToCreateSchema(dataSource);
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setDropFirst(true);
liquibase.setDataSource(dataSource);
liquibase.setDefaultSchema("the_schema");
// This and all supported liquibase changelog files are copied onto my classpath
// via the maven assembly plugin. The config to do this has been omitted for the
// sake of brevity
// see this URL for how I did it:
// https://blog.sonatype.com/2008/04/how-to-share-resources-across-projects-in-maven/
liquibase.setChangeLog("classpath:/test/location/of/liquibase.changelog-root.yml");
return liquibase;
}
private void tryToCreateSchema(DataSource dataSource) throws SQLException {
String CREATE_SCHEMA_QUERY = "CREATE SCHEMA IF NOT EXISTS test";
dataSource.getConnection().createStatement().execute(CREATE_SCHEMA_QUERY);
}
}
MyIT.java:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=CommonConfig.class)
@ActiveProfile("integration")
@Import(TestPostgresConfig.class)
public class MyIT {
@ClassRule
public static PostgreSQLContainer psqlContainer = new PostgreSQLContainer("postgres:13.1")
.withDatabseName("test-database-instance")
.withUsername("divdiff")
.withPassword("theseAreNotTheDroidsForYou123");
@BeforeClass
public static void init() {
System.setProperty("spring.datasource.url", "jdbc:postgresql://"
+ psqlContainer.getHost() + ":"
+ psqlContainer.getMappedPort(5432) + "/"
+ psqlContainer.getDatabaseName()));
System.setProperty("spring.datasource.username", psqlContainer.getUsername());
System.setProperty("spring.datasource.password", psqlContainer.getPassword());
}
@Before
public void setUp() {
// code to set up my test
}
@Test
public void testMyCodeEndToEnd() {
// my test implementation
}
}
MyConfig.java:
@Configuration
@ComponentScan(basePackages = "my.code")
@EntityScan("my.code")
@Slf4j
public class MyConfig {
@Bean
public KeyStore keyStore() {
//load keystore and set javax.net.ssl.keystore* properties
}
@Bean
public KeyStore trustStore() {
//load truststore and set javax.net.ssl.truststore* properties
}
@Bean
public RestTemplate restTemplate() {
//Set up and load SSL Context with key and trust store
//Create HTTPClient and connection stuff
//Look at this link for a similar set up
//https://www.baeldung.com/rest-template
}
}
application-integration.yml
spring:
jpa:
properties:
hibernate:
enable_lazy_load_no_trans: true
profiles:
active: default
server:
ssl:
# My key and trust store values
application:
unrelated-app-properties:
# propertie values below
Package structure:
app-project/src/main/java/com/my/code/MyApplication.java
app-project/src/main/java/com/my/code/service/MyService.java
app-project/src/test/java/my/code/OTHER-TEST-CLASSES-LIVE-HERE...
app-project/src/test/java/integration/MyIT.java
app-project/src/test/java/integration/TestPostgresConfig.java
app-project/src/test/resources/application-integration.yml
my-common-project/src/main/java/common/config/MyConfig.java
YOUR HELP IS MUCH APPRECIATED:!! :D
I'm an idiot. The maven dependency I brought into for my tests was using provided scope instead of test:
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>project-with-db-changelogs</artifactId>
<version>1.0-SNAPSHOT</version>
<classifier>resources</classifier>
<type>zip</type>
<scope>provided</scope>
</dependency>
When it should have been test scope:
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>project-with-db-changelogs</artifactId>
<version>1.0-SNAPSHOT</version>
<classifier>resources</classifier>
<type>zip</type>
<scope>test</scope>
</dependency>
Per this link , "This is available only in compile-classpath and test-classpath", hence the liquibase code was being run in both my tests and the resulting jar. #amateur-hour
You can defile liqubase context as test
<changeSet author="name" id="id-of-file" context="test">
and have an application property like: spring.liquibase.contexts=test
and add a liquibase bean like:
@Value("${spring.liquibase.contexts}") private String liquibaseContexts;
@Bean
public SpringLiquibase liquibase() {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setDataSource(localDatabaseDataSource);
liquibase.setShouldRun(liquibaseEnabled);
liquibase.setChangeLog(localDatabaseLiquibaseChangeLog);
liquibase.setContexts(liquibaseContexts);
return liquibase;
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.