Search code examples
spring-bootunit-testingliquibasespring-boot-test

Tests not working for multiple datasource with spring boot liquibase


I am creating unittesting for spring boot application having multiple datasources. (Configuration is heavily inspired from this answer)

@Configuration
public class DatasourceConfig {

    @Primary
    @Bean
    @ConfigurationProperties(prefix = "datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "datasource.primary.liquibase")
    public LiquibaseProperties primaryLiquibaseProperties() {
        return new LiquibaseProperties();
    }

    @Bean
    public SpringLiquibase primaryLiquibase() {
        return springLiquibase(primaryDataSource(), primaryLiquibaseProperties());
    }

    @Bean
    @ConfigurationProperties(prefix = "datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "datasource.secondary.liquibase")
    public LiquibaseProperties secondaryLiquibaseProperties() {
        return new LiquibaseProperties();
    }

    @Bean
    public SpringLiquibase secondaryLiquibase() {
        return springLiquibase(secondaryDataSource(), secondaryLiquibaseProperties());
    }

    private static SpringLiquibase springLiquibase(DataSource dataSource, LiquibaseProperties properties) {
        SpringLiquibase liquibase = new SpringLiquibase();
        liquibase.setDataSource(dataSource);
        liquibase.setChangeLog(properties.getChangeLog());
        liquibase.setContexts(properties.getContexts());
        liquibase.setDefaultSchema(properties.getDefaultSchema());
        liquibase.setDropFirst(properties.isDropFirst());
        liquibase.setShouldRun(properties.isEnabled());
        liquibase.setLabels(properties.getLabels());
        liquibase.setChangeLogParameters(properties.getParameters());
        liquibase.setRollbackFile(properties.getRollbackFile());
        return liquibase;
    }


...

}

and

datasource:
  primary:
    url: jdbc:mysql://localhost/primary
    username: username
    password: password
    liquibase:
      change-log: classpath:/db/changelog/db.primary.changelog-master.xml
  secondary:
    url: jdbc:mysql://localhost/secondary
    username: username
    password: password
    liquibase:
      change-log: classpath:/db/changelog/db.secondary.changelog-master.xml

The application is running properly.

The problem is unit testing is failing with this configuration. I have used h2 as embedded DB. Added h2 as test dependency and added h2 in datasource URL as well in application.yaml for testing.

application.yaml for testing

management:
  endpoints.web.exposure.include: "*"
  security.enabled: false
spring:
  zipkin:
    discoveryClientEnabled: false
    sender:
      type: kafka
      #type: web
  liquibase:
    enabled: false
  datasource:
    url: "jdbc:h2:mem:testdb"
    jdbc-url: "jdbc:h2:mem:testdb"
    username: sa
    password:
  secondary-datasource:
    url: "jdbc:h2:mem:testdb"
    jdbc-url: "jdbc:h2:mem:testdb"
    username: sa
    password:
datasource:
 primary-liquibase:
   liquibase:
     url: "jdbc:h2:mem:testdb"
     username: sa
     password:
 secondary-liquibase:
   liquibase:
     url: "jdbc:h2:mem:testdb"
     username: sa
     password:
liquibase:
  enable: false

Unit testing file

package com.foo.bar.car;

import com.foo.bar.car.utils.KSUIDGenerator;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureTestDatabase
// https://stackoverflow.com/a/58786742/1534925
//@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
//@AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2)
class CarApplicationTests {

    @Test
    public void contextLoads() {
        String h = "Hello World!";
        Assertions.assertEquals(h, "Hello World!");
    }

    @Test
    public void testKSUIDGeneration() {
        Assertions.assertNotNull(KSUIDGenerator.generateKSUID());
    }
}

I have created a repository to demonstrate this.

When I do gradlew clean check it gives me following error

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [liquibase.integration.spring.SpringLiquibase]: Factory method 'primaryLiquibase' threw exception; nested exception is java.lang.IllegalArgumentException: No visible constructors in class org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration$EmbeddedDataSourceFactoryBean

Not sure what configuration change I am missing.


Solution

  • There are several adjustments to be done to make it work:

    1. Exclude Autoconfiguration for DB and Liquibase in your CarApplication.java

      @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, LiquibaseAutoConfiguration.class })

    2. Get rid of sping prefix in your configuration (both in app and test properties)

    3. Add liquibase changelog location to test properties

    Double check with the setup below in case it wont work after all listed above adjustments (I changed things here and there the way I usually code to make it comfortable to me):

    CarApplication.java

    @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, 
                                       LiquibaseAutoConfiguration.class })
    public class CarApplication {
      public static void main(String[] args) {
        SpringApplication.run(CarApplication.class, args);
      }
    }
    

    application.yaml

    server:
      port: 9999
    
    datasource:
      url: jdbc:postgresql://localhost:8888/foo
      jdbcUrl: jdbc:postgresql://localhost:5432/foo
      username: username
      password: password
      driverClassName: org.postgresql.Driver
      liquibase:
        change-log: classpath:db-changelog/primary.xml
    
    secondary-datasource:
      url: jdbc:postgresql://localhost:8888/bar
      jdbcUrl: jdbc:postgresql://localhost:5432/bar
      username: username
      password: password
      driverClassName: org.postgresql.Driver
      liquibase:
        change-log: classpath:db-changelog/secondary.xml
    

    DatabaseConfig.java

    @Configuration
    public class DatabaseConfig {
    
        @Bean
        @Primary
        @ConfigurationProperties(prefix = "datasource")
        public DataSource primaryDataSource() {
            return DataSourceBuilder.create().build();
        }
    
        @Bean
        @ConfigurationProperties(prefix = "secondary-datasource")
        public DataSource secondaryDataSource() {
            return DataSourceBuilder.create().build();
        }
    
        @Bean
        @ConfigurationProperties(prefix = "datasource.liquibase")
        public LiquibaseProperties primaryLiquibaseProperties() {
            return new LiquibaseProperties();
        }
    
        @Bean("liquibase")
        public SpringLiquibase primaryLiquibase() {
            return springLiquibase(primaryDataSource(), primaryLiquibaseProperties());
        }
    
        @Bean
        @ConfigurationProperties(prefix = "secondary-datasource.liquibase")
        public LiquibaseProperties secondaryLiquibaseProperties() {
            return new LiquibaseProperties();
        }
    
        @Bean
        public SpringLiquibase secondaryLiquibase() {
            return springLiquibase(secondaryDataSource(), secondaryLiquibaseProperties());
        }
    
        private static SpringLiquibase springLiquibase(DataSource dataSource, LiquibaseProperties properties) {
            SpringLiquibase liquibase = new SpringLiquibase();
            liquibase.setDataSource(dataSource);
            liquibase.setChangeLog(properties.getChangeLog());
            liquibase.setContexts(properties.getContexts());
            liquibase.setDefaultSchema(properties.getDefaultSchema());
            liquibase.setDropFirst(properties.isDropFirst());
            liquibase.setShouldRun(properties.isEnabled());
            liquibase.setLabels(properties.getLabels());
            liquibase.setChangeLogParameters(properties.getParameters());
            liquibase.setRollbackFile(properties.getRollbackFile());
            return liquibase;
        }
    }
    

    test application.yaml

    datasource:
      url: "jdbc:h2:mem:testdb"
      jdbc-url: "jdbc:h2:mem:testdb"
      username: sa
      password:
      liquibase:
        change-log: classpath:db-changelog/primary.xml
    
    secondary-datasource:
      url: "jdbc:h2:mem:testdb"
      jdbc-url: "jdbc:h2:mem:testdb"
      username: sa
      password:
      liquibase:
        change-log: classpath:db-changelog/secondary.xml
    

    CarApplicationTest.java

    @SpringBootTest
    class CarApplicationTests {
        @Test
        public void contextLoads() {
            String h = "Hello World!";
            Assertions.assertEquals(h, "Hello World!");
        }
        @Test
        public void testKSUIDGeneration() {
            Assertions.assertNotNull(KSUIDGenerator.generateKSUID());
        }
    }
    

    Result :-)