1

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.

Akshay
  • 3,558
  • 4
  • 43
  • 77

1 Answers1

1

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 :-)

dmytro
  • 1,141
  • 11
  • 13