SpringBootTest에 HSQL 데이터베이스 적용기

2024. 5. 3. 19:39스프링 (Spring)/스프링 데이터 (Spring Data)

 


HSQL?

 

Spring 문서의 Data 항목을 보면 간단하게 만들 수 있는 Embeded DataSource를 소개해줍니다.

 

Data

The Spring Framework provides extensive support for working with SQL databases, from direct JDBC access using JdbcClient or JdbcTemplate to complete “object relational mapping” technologies such as Hibernate. Spring Data provides an additional level of

docs.spring.io

 

그냥 쉽게 말해 외부 데이터베이스 연결 없이 자체 구축되는 데이터베이스라고 이해하고 있습니다.

후보군은 H2HSQLDerby 이렇게 있는데, 저는 HSQL로 선택했습니다.

이유는... 가이드가 있기 때문이죠

 

 

 

 

 

 


Configure HSQLDB for Test

 

시작 전에 application.yml 파일을 기존 프로파일과 구분되도록 test 디렉터리에 별도로 만들어줍니다.

../test/resources

 

가이드를 읽으면 바로 마주치는 글이 있었는데

 

딱히 필요는 없다만, 있어도 성능에는 크게 문제가 되지 않으니 그냥 함께 반영해봅니다.

// application-test.yml

spring:
  config:
    name: App
      
  datasource:
    generate-unique-name: true

...

 

그리고 build.gradle.kts 에서 다음 한 줄을 추가해줍니다.

(spring-boot-starter-data-jpa은 이미 있다고 가정합니다)

dependencies {
    ...
    
    runtimeOnly(testImplementation(group = "org.hsqldb", name = "hsqldb"))
}

 

끝났습니다.

 

이제 테스트 코드로 돌아와서,

import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles

@ActiveProfiles("test")
@SpringBootTest
class ApplicationTest {

    @Test
    fun contextLoads() {
    }

}

 

실행해주면...!

 

잘 되네요.

 

 

 

 

 


Situation

 

이걸 마지막에 위치했던 이유는, 그냥 푸념이라서 영양가가 없기 때문입니다.

 

발단은 API 문서 생성을 위한 Restdocs 적용이었네요. 적용기는 다음 글에서 다루는 걸로 하고... (공식 문서 미리보기)

실행할 테스트는 아래처럼 생겼습니다.

@SpringBootTest
class TestingRestdocsApplicationTests {

    @Test
    @Throws(Exception::class)
    fun contextLoads() {
    }

}

 

테스트는 실제 데이터베이스와 연결하지 않고 mock을 사용하므로 (참고 : Mockito를 이용해 JpaRepository 테스트하기)

실제로 제 프로젝트에서는 다음과 같이 생겼더라죠.

@SpringBootTest(classes = [TestDataSourceConfiguration::class])
class TestingRestdocsApplicationTests {

    @Test
    fun contextLoads() {
    }

}

 

아이디어는 Spring에서 Gmail 보내기에서 나왔습니다.

 

 

그러나 믿고 싶지 않았던 사실은...

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment] due to: Unable to determine Dialect without JDBC metadata (please set 'jakarta.persistence.jdbc.url' for common cases or 'hibernate.dialect' when a custom Dialect implementation must be provided)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1786)
...
caused by: org.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment] due to: Unable to determine Dialect without JDBC metadata (please set 'jakarta.persistence.jdbc.url' for common cases or 'hibernate.dialect' when a custom Dialect implementation must be provided)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:276)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:238)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:215)
at org.hibernate.engine.jdbc.internal.JdbcServicesImpl.configure(JdbcServicesImpl.java:52)
at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.configureService(StandardServiceRegistryImpl.java:136)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:247)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:215)
at io.hypersistence.utils.hibernate.type.HibernateTypesContributor.contribute(HibernateTypesContributor.java:78)
at org.hibernate.boot.internal.MetadataBuilderImpl.applyTypes(MetadataBuilderImpl.java:295)

 

이제 피할 수 없다고 생각, test 전용 profile을 만들기로 합니다.

../test/resources

 

spring:
  config:
    name: App

  mail:
    properties:
      "[mail.smtp.connectiontimeout]": 5000
      "[mail.smtp.timeout]": 3000
      "[mail.smtp.writetimeout]": 5000

logging:
  level:
    root: INFO

server:
  error:
    include-message: always

 

이제 돌아와 ActiveProfile을 추가해줍니다.

@ActiveProfiles("test")
@SpringBootTest(classes = [TestDataSourceConfiguration::class])
class TestingRestdocsApplicationTests {

    @Test
    fun contextLoads() {
    }

}

 

그리고 TestDataSourceConfiguration에서 광기의 Mocking 작업을 했으나...

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionRepository' defined in ...SessionRepository defined in @EnableJpaRepositories declared on DolphinDataSourceConfiguration: Cannot resolve reference to bean 'jpaMappingContext' while setting bean property 'mappingContext'
...
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': Metamodel must not be null
...
Caused by: java.lang.IllegalArgumentException: Metamodel must not be null

 

수습하지 못할 지경까지 와버렸네요.

저 Repository 빈들을 등록할 마음은 없습니다.

또 아무리 리서치를 하고 설정을 바꿔도 결국 의미는 없었고요. (무의미한 내 시간...)

 

그래서 그냥 인메모리 데이터베이스를 적용하기로 마음 먹습니다.