SpringBoot: Configuring Spring DataSource for Tests
Asked Answered
N

3

5

I have a SpringBoot app.

I have created this test:

@ContextConfiguration(classes={TestConfig.class})
@RunWith(SpringRunner.class)
@SpringBootTest
public class SuncionServiceITTest {
    @Test
    public void should_Find_2() {
        // TODO
    }
}

where

@Configuration
@EnableJpaRepositories(basePackages = "com.plats.bruts.repository")
@PropertySource("local-configuration.properties")
@EnableTransactionManagement
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
public class TestConfig {
}

and local configuration.properties:

spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=sa

but when I run the test. I got this error:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'entityManagerFactory' available

I also tried with:

@EnableJpaRepositories(basePackages = "com.plats.bruts.repository", entityManagerFactoryRef="emf")

but then I have the error:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'emf' available

Nowicki answered 4/11, 2020 at 8:41 Comment(0)
T
4

Looks like you are missing below starter dependency. This starter dependency has all the necessary dependencies needed to configure the jpa repositories.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Talebearer answered 6/11, 2020 at 16:44 Comment(0)
I
2

This is an approach of how to configure several data sources in one application. I have tested it for spring-webmvc and graphql-java, but I think it can be useful for spring-boot as well.


Configure several data sources with spring-data-jpa

For each database we should enable JPA repositories and specify the base packages for the corresponding interfaces. Also for each database we should specify the entity manager factory and the base packages for corresponding entities, as well as the transaction manager and the data source, of course.

To do this, we'll include in our application one configuration class for data JPA and three inner classes for each database. See the Simple GraphQL implementation.

DataJpaConfig.java

package org.drakonoved.graphql;

@Configuration
@PropertySource(value = "classpath:resources/application.properties", encoding = "UTF-8")
public class DataJpaConfig {
    private final String basePackage = "org.drakonoved.graphql";

    @EnableJpaRepositories(
            basePackages = basePackage + ".repository.usersdb",
            entityManagerFactoryRef = "usersdbEntityManagerFactory",
            transactionManagerRef = "usersdbTransactionManager")
    public class UsersDBJpaConfig {
        @Bean("usersdbEntityManagerFactory")
        public LocalContainerEntityManagerFactoryBean usersDBEntityManagerFactoryBean(
                @Value("${datasource.usersdb.script}") String script) {
            return createEntityManagerFactoryBean(
                    script, "usersdb", basePackage + ".dto.usersdb");
        }

        @Bean("usersdbTransactionManager")
        public PlatformTransactionManager usersDBTransactionManager(
                @Qualifier("usersdbEntityManagerFactory")
                        LocalContainerEntityManagerFactoryBean factoryBean) {
            return new JpaTransactionManager(factoryBean.getNativeEntityManagerFactory());
        }
    }

    @EnableJpaRepositories(
            basePackages = basePackage + ".repository.rolesdb",
            entityManagerFactoryRef = "rolesdbEntityManagerFactory",
            transactionManagerRef = "rolesdbTransactionManager")
    public class RolesDBJpaConfig {
        @Bean("rolesdbEntityManagerFactory")
        public LocalContainerEntityManagerFactoryBean rolesDBEntityManagerFactoryBean(
                @Value("${datasource.rolesdb.script}") String script) {
            return createEntityManagerFactoryBean(
                    script, "rolesdb", basePackage + ".dto.rolesdb");
        }

        @Bean("rolesdbTransactionManager")
        public PlatformTransactionManager rolesDBTransactionManager(
                @Qualifier("rolesdbEntityManagerFactory")
                        LocalContainerEntityManagerFactoryBean factoryBean) {
            return new JpaTransactionManager(factoryBean.getNativeEntityManagerFactory());
        }
    }

    @EnableJpaRepositories(
            basePackages = basePackage + ".repository.usersnrolesdb",
            entityManagerFactoryRef = "usersnrolesdbEntityManagerFactory",
            transactionManagerRef = "usersnrolesdbTransactionManager")
    public class UsersNRolesDBJpaConfig {
        @Bean("usersnrolesdbEntityManagerFactory")
        public LocalContainerEntityManagerFactoryBean usersNRolesDBEntityManagerFactoryBean(
                @Value("${datasource.usersnrolesdb.script}") String script) {
            return createEntityManagerFactoryBean(
                    script, "usersnrolesdb", basePackage + ".dto.usersnrolesdb");
        }

        @Bean("usersnrolesdbTransactionManager")
        public PlatformTransactionManager usersNRolesDBTransactionManager(
                @Qualifier("usersnrolesdbEntityManagerFactory")
                        LocalContainerEntityManagerFactoryBean factoryBean) {
            return new JpaTransactionManager(factoryBean.getNativeEntityManagerFactory());
        }
    }

    //////// //////// //////// //////// //////// //////// //////// ////////

    private LocalContainerEntityManagerFactoryBean createEntityManagerFactoryBean(
            String script, String dbname, String packagesToScan) {
        var factoryBean = new LocalContainerEntityManagerFactoryBean();
        factoryBean.setDataSource(new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .setName(dbname)
                .addScript(script)
                .build());
        factoryBean.setPersistenceUnitName(dbname + "EntityManagerFactory");
        factoryBean.setPackagesToScan(packagesToScan);
        factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());

        var properties = new HashMap<String, Object>();
        properties.put("hibernate.hbm2ddl.auto", "none");
        properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
        factoryBean.setJpaPropertyMap(properties);

        return factoryBean;
    }
}
Infuscate answered 4/11, 2020 at 15:10 Comment(0)
B
2

I prefer to use following approach (i don't like to create own bean configurator). As @svr correctly noticed (see previous answer) you don't add starter package for beans auto configure. I usually create different profiles: for local app running, for dev, prod and finally last one for tests. In my test profile (application-functests.yml) i configure all settings that needs for my functional tests (datasources, hibernate, cache and so on), i.e. my application-functests.yml of one of my projects:

spring:
  http:
    encoding:
      charset: UTF-8
      enabled: true
  profiles: functests
  jpa:
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        enable_lazy_load_no_trans: true
        naming:
            physical-strategy: com.goodt.drive.orgstructure.application.utils.SnakePhysicalNamingStrategy
    hibernate:
      ddl-auto: none
    database-platform: org.hibernate.dialect.PostgreSQL9Dialect    
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/monitor_service_functests
    username: developer
    password: 123
    sql-script-encoding: UTF-8
  liquibase:
    change-log: classpath:db/changelog/changelog.xml

I have only specify profile for running test, therefore all of my functional tests are using functests profile, i.e.:

@SpringBootTest
@ActiveProfiles("functests")
public class TestEventRepository extends FunctionalTestBase {
    
    @Test
    public void testGetAll() {
        Iterable<EventEntity> eventIterable = dbContext.getEventDataSource().findAll();
        Iterator<EventEntity> it = eventIterable.iterator();
        List<EventEntity> actualEvents = new ArrayList<>();
        while (it.hasNext()) {
            actualEvents.add(it.next());   
        }
        List<EventCheckData> expectedEvents = new ArrayList<>() {{
            add(new EventCheckData(1L, 1L, "body 1", 1L, 1L));
            add(new EventCheckData(2L, 2L, "body 2", 2L, 2L));
            add(new EventCheckData(3L, 3L, "body 3", 3L, 1L));
            add(new EventCheckData(4L, 1L, "body 4", 2L, 1L));
            add(new EventCheckData(5L, 2L, "body 5", 1L, 2L));
        }};
        EventSimpleChecker.check(expectedEvents, actualEvents);
    }
}

In my code example i have base test class - FunctionalTestBase which is used for interact with liquibase (toggle it to apply migrations)

Beret answered 6/11, 2020 at 18:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.