오직 웹 계층과 관련있는 의존성 클래스들만 로드하는 어노테이션이다.
즉 WebMvcConfigurer
, HandlerMethodArgumentResolver
부터 시작하여 시큐리티, 서블릿 필터, 스프링 인터셉터, @ControllerAdvice
, @JsonComponent
와 같이 컨트롤러 관련 어노테이션들이 붙은 클래스를 모두 포함한다.
@SpringBootTest
처럼 모든 의존성을 가져오지 않기 때문에, @Component
, @Service
, @Repository
가 필요 없는 컨트롤러 테스트를 할 때 적합하다.
@WebMvcTest
를 선언하고, 실행을 시켰는데 다음과 같은 에러가 발생할 수 있다.
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaAuditingHandler': Cannot resolve reference to bean 'jpaMappingContext' while setting constructor argument
Caused by: java.lang.IllegalArgumentException: JPA metamodel must not be empty
jpaMappingContext
클래스를 찾을 수 없다는 것인데, 왜 이런 문제가 발생할까?
@WebMvcTest
를 붙였어도, 우선적으로 SpringBootApplication
클래스를 찾은 이후에 선택적으로 필요한 빈들만 로드하게 된다. @SpringBootApplication
이 자동 의존성 설정을 해주기 때문이다.
따라서 SpringBootApplication
클래스에 작성자/작성일자 자동화를 위해 붙여놓은 @EnableJpaAuditing
이 존재하니, 자동으로 JPA 설정을 위해jpaMappingContext
가 필요했고 테스트 클래스에 주입을 안해주었으니 에러가 난 것이 원인이였다.
@SpringBootApplication
@EnableJpaAuditing // 원인
@EnableConfigurationProperties
class AhachulBackendApplication
...
간단하게 JpaMetamodelMappingContext
를 주입해주면 해결할 수 있다.
하지만 컨트롤러 테스트에서 쓰이지 않기 때문에 불필요한 의존성일 수도 있으므로, @EnableJpaAuditing
과 같이 특정 기술에 종속적인 어노테이션들은 모두 JpaConfig
이라는 설정 클래스를 만들어서 모아 놓는것이 좋다.
@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
class JpaConfig {
...
}
모든 빈을 로드해서 의존 관계를 설정하는 테스트 어노테이션으로, 그만큼 속도가 느리다는 단점이 존재한다.
주의할 점은 해당 링크에 따르면, 서비스 레이어와 같은 경우 mock
에 필요한 어노테이션들을 제외하고는 @SpringBootTest
을 사용하지 않는 것이 좋다고 한다. 독립적으로 모듈화 되어야 하는 비즈니스 로직에 관련된 테스트이기 때문에, 설정에 의존해서는 안되기 때문이다.
AOP 테스트를 할 때, @SpringBootTest
와 내부 클래스에 @Configuration
을 같이 사용하는 경우 프록시가 동작하지 않는 문제 발생할 수 있다. 예를 들어 아래의 예제에서 child.childMethod()
를 호출하면, 빈으로 등록해주었는데도 Proxy가 적용이 안된 스프링 빈이 출력된다.
child Proxy=class ...AtTargetAtWithinTest$Child
//@Import(AtTargetWithinTest.Config.class)
@SpringBootTest
public class AtTargetWithinTest {
...
@Configuration
static class Config {
@Bean
public Parent parent() {
return new Parent();
}
@Bean
public Parent.Child child() { // @ClassAop가 붙은 클래스
return new Parent.Child();
}
@Bean
public Parent.AtTargetAtWithinAspect atTargetAtWithinAspect() {
return new Parent.AtTargetAtWithinAspect();
}
}
}
내부 클래스에 선언된 @Configuration
클래스 경로가 자동으로 컴포넌트 스캔의 기본패키지로 지정되기 되어 우선권을 가지기 때문에, 이후 모든 스프링 부트의 다양한 설정들이 먹히지 않게 된다.
따라서 AOP
적용에 반드시 필요한 AutoProxy
와 같은 스프링 부트가 자동으로 만들어주는 AOP
기본 클래스들도 빈으로 등록되지 않아 프록시가 동작하지 않는다.
@EnableAspectJProxy
+ @Configuration
: 기존의 src/main
모듈 설정과 AOP
설정을 따로 해주어야 하므로 번거롭다.@TestConfiguration
: 권장하는 방법으로, src/main
전체 모듈의 기본적인 설정을 유지하면서 추가적으로 테스트용 설정을 추가할 수 있다. @TestConfiguration
static class Config {
}
오직 JPA와 관련된 설정들만 로드하는 테스트 어노테이션이다.
특별히 지정된 DataSource
가 없다면, h2
와 같은 내장된 인메모리 데이터베이스를 사용해서 테스트한다. 또한, @SpringBootTest
+ @Transactional
을 붙였을 때와 똑같이 모든 테스트의 끝에서 커밋이 아닌 롤백을 해줌으로써 테스트의 독립성을 지켜준다는 장점이 있다.
JPA 엔티티 ID 생성 타입을 GenerationType = Identity
로 지정하면, 롤백을 해줌에도 INSERT 쿼리가 날라가는 상황이 발생할 수 있다.
물론 INSERT
문과 SELECT
문을 제외한 UPDATE
나 DELETE
문은 날라가지 않았지만, 어느 변경 사항도 실제 DB에 반영이 되어서는 안되는데 왜 이러한 문제가 발생하는 것일까?
GenerationType = Identity
전략은 기본키 생성을 DB에 위임한다.
따라서 INSERT
가 될때에는 ID가 NULL
인 상태이고, 영속성 컨텍스트에 관리되기 위해서 예외적으로 커밋 시점이 아닌 영속 시점에 INSERT 문을 날려 식별자를 조회한 후 가져와서 저장하게 된다. 그렇기에 롤백을 했음에도 생성 쿼리가 나간다.
GenerationType.SEQUENCE
를 사용하면 커밋 시점에 INSERT
쿼리를 날려서 문제 해결이 가능하지만, Oracle, DB2, and Postgres 데이터베이스에서만 사용이 가능하므로 현재 상황에서는 전략을 그대로 가져갔다.
참고 자료
Does @WebMvcTest require @SpringBootApplication annotation?
AOP 테스트 문제