단위 테스트는 해당 컴포넌트에 대해서만 진행하는 테스트이며 해당 컴포넌트에 있는 코드들만 테스트하면 된다.
그래서 단위 테스트를 할 때 해당 컴포넌트에서 필요한 환경만 구성해야한다. 즉 Application Context 에 모든 Bean 을 띄워주는 @SpringBootTest
는 반드시 지양해야한다.
@SpringBootTest
는 말 그대로 SpringBoot 의 환경을 제공한다. 모든 Bean 을 띄우고 SpringBoot 의 기능을 모두 제공한다. 테스트 할 때는 편할 수 있지만, 굉장히 느리다는 치명적인 단점이 있다.
그래서 각 상황에 맞게 환경을 컴팩트하게 구성해야한다.
@SpringJunitConfig
이 만드려는 테스트 환경의 주축이라고 할 수 있다.
@SpringJunitConfig
는 아래와 같이 구성돼 있다.
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SpringJUnitConfig
여기서 눈여겨 볼 것은 @ExtendWith(SpringExtension.class)
과 @ContextConfiguration
두개다.
이 어노테이션은 JUnit 5 테스트 라이프사이클에 맞춰서 Spring Application Context 를 만들고 없애주며 테스트 환경에 Spring 환경을 입혀준다.
이 어노테이션은 스프링 테스트 환경에서 테스트에 필요한 스프링 빈들을 설정할 수 있게 해준다.
Jdbc 를 사용하려면 DataSource 가 있어야한다.
그래서 아래처럼 DataSource 를 Bean 으로 등록한다
@SpringJUnitConfig
public class DataSourceTestSupport {
@TestConfiguration
static class DataSourceConfig {
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.driver-class-name}")
private String driverClasName;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Bean
public DataSource dataSource() {
DataSource dataSource = DataSourceBuilder.create()
.url(url)
.driverClassName(driverClasName)
.username(username)
.password(password)
.type(HikariDataSource.class)
.build();
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.execute(SqlFixture.DDL);
return dataSource;
}
}
}
먼저 이 코드의 사용법부터 보자면 간단하게 DataSourceTestSupport
클래스를 상속하면 된다.
그러면 @SpringJUnitConfig
를 통해서 Spring Application Context 환경을 제공받고 등록한 Bean
을 사용할 수 있다.
근데 위 코드에는 해결해야할 문제가 두 가지 있다.
첫 번째는 @Value
어노테이션의 적용이 안된다.
두 번째는 트랜잭션을 적용해야한다(각 테스트 후에 롤백하기 위함)
@SpringJUnitConfig
어노테이션은 Spring Application Context 환경을 제공하지만 빈 깡통이기 때문에 Spring Boot 가 해주는 모든 기능을 제공하지 않는다.
그래서 Property 파일 소스가 포함되지 않아서 @Value
어노테이션이 적용이 되지 않는다.
그래서 직접해줘야 하는데, @TestPropertySource("classpath:application.properties")
이 어노테이션을 추가해서 직접 프로퍼티 파일을 등록해줘야한다.
Spring Boot 에서는 별도의 설정을 하지 않아도 @Transactional
어노테이션 하나로 간단하게 트랜잭션 기능을 제공받을 수 있다.
하지만 여기선 Spring Boot 의 기능을 제공받지 않기 때문에 이 또한 직접 해줘야한다.
Spring Boot 는 @EnableTransactionManagement
를 통해서 트랜잭션을 제공한다.
슬프게도 @SpringJUnitConfig
에서는 @EnableTransactionManagement
마저 적용되지 않는다.
그래서 결국 트랜잭션을 직접 Bean
으로 등록해야한다
방법은 간단하다
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
이렇게 DataSourceTransactionManager
를 Bean
으로 등록하면 된다. 물론 이걸 사용하려면 위에서 DataSource
에 대한 Bean
을 등록해줘야한다
@SpringJUnitConfig
@Transactional
@TestPropertySource("classpath:application.properties")
public abstract class DataSourceTestSupport {
@TestConfiguration
static class DataSourceConfig {
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.driver-class-name}")
private String driverClasName;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Bean
public DataSource dataSource() {
DataSource dataSource = DataSourceBuilder.create()
.url(url)
.driverClassName(driverClasName)
.username(username)
.password(password)
.type(HikariDataSource.class)
.build();
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.execute(SqlFixture.DDL);
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
}
위 처럼 구성하면 DataSource 를 주입받아 사용할 수 있고 트랜잭션까지 제공받을 수 있다.