简体   繁体   中英

Test Config Beans Loaded into Spring Context During App Run

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM