👩🏻‍💻 Programming/SpringBoot

@SpringBootTest, @AutoConfogureMockMvc, 그리고 @WebMvcTest

한국의 메타몽 2022. 2. 10. 04:41

 

JUnit과 단위테스트

 

SpringBoot에서 JUnit를 사용하여 테스트 코드를 작성할때, 대표적으로 @SpringBootTest@WebMvcTest를 사용하는 경우가 많다.

처음에는 둘의 차이를 간단하게 @SpringBootTest는 모든 빈을 가져와서 속도가 느리고 @WebMvcTest는 필요한 빈만 가져와서 속도가 빠르다 정도로 이해를 했지만, 정확히 개념을 이해하지 못하고 사용한 탓에 테스트 케이스 작성 실패를 겪은적이 있었다. 내가 쓴 테스트 케이스가 어떤 상황에서, 무엇을 목적으로 돌아가는 테스트 케이스인지를 보다 정확히 이해하기 위해 개념을 정리한다.

 

 

 

@SpringBootTest + @AutoConfigureMockMvc

 

1) 특징 

- 프로젝트 내부에 있는 스프링 빈을 모두 등록하여 테스트에 필요한 의존성을 추가

- 실제 운영 환경에서 사용될 클래스들을 통합하여 테스트

- 단위 테스트와 같이 기능 검증을 위한 것이 아니라, Spring Framework에서 전체적으로 Flow가 제대로 동작하는지 검증하기 위해 사용

 

2) 장점

- 애플리케이션의 설정, 모든 Bean을 모두 로드하기 때문에 운영환경과 가장 유사한 테스트가 가능

 

3) 단점

- 테스트 단위가 크기 때문에 디버깅이 까다로움

- 어플리케이션의 설정, 모든 Bean을 로드하기 때문에 시간이 오래 걸림

 


 

Q) @RunWith은 언제쓰나요?

Spring 공식 문서

만약 JUnit4를 사용한다면 @RunWith(SpringRunner.class)을 같이 추가해줘야지만 Annotation이 무시되지 않는다.

반면 JUnit5를 사용한다면 @RunWith, @ExtendWith 등을 추가해줄 필요가 없다.

 

Q) @AutoConfigureMockMvc는 언제쓰나요?

- Mock 테스트시 필요한 의존성을 제공

@Autowired
MockMvc mvc;

- MockMvc 객체를 통해 실제 컨테이너가 실행되는 것은 아니지만 로직상 테스트 가능

- print() 함수를 통해 좀 더 테일한 테스트 결과를 볼 수 있음

- @WebMvcTest가 아닌 @SpringBootTest에서도 Mock 테스트를 가능하게 해주는 역할

 

Q ) @ActiveProfiles는 언제쓰죠?

- 프로파일 전략을 사용 중이라면 원하는 프로파일 환경값을 설정 가능

@ActiveProfiles("test") // test 프로파일 사용

 

Q ) Mock이 뭐죠?

- 테스트 작성을 위한 환경 구축이 어렵거나, 테스트가 특정 경우 및 순간에 의존적인 경우 테스트 작성 시간을 단축하기 위해 사용

- 실제를 흉내낸 가짜 객체

- Mock 객체는 Mock일 뿐, 실제 객체를 작동했을 때 예상대로 작동하지 않을 수 있음

 

Q ) MockBean이 뭐죠?

- Mock 객체를 빈으로 등록

- Spring의 ApplicationContext는 Mock 객체를 빈으로 등록하며, 이미 동일한 타입의 동일한 객체가 빈으로 등록되어있을 경우, 해당 빈은 선언한 @MockBean으로 대체됨

 


 

[ 사용 예시 ]

@AutoConfigureMockMvc
@SpringBootTest
@ActiveProfiles("test")
public class FullfillmentControllerTest {

    @Autowired
    protected MockMvc mockMvc;
    @MockBean
    private FullfillmentProvisionService fullfillmentProvisionService;
    @MockBean
    private FullfillmentlicationService fullfillmentApplicationService;
        when(fullfillmentProvisionService.provideItem(any())).thenThrow(new ItemNotFoundException(itemCode));

        // then
        mockMvc.perform(post("/v1.1/fullfillment/load")
                .content(objectMapper.writeValueAsString(request))
                .contentType(MediaType.APPLICATION_JSON)
            )
            .andExpect(status().isUnprocessableEntity())
            .andExpect(jsonPath("$.header").exists())
            .andExpect(jsonPath("$.body").isEmpty())
            .andDo(print());

위 코드는 @SpringBootTest, @AutoConfogureMockMvc, 동시에 어찌보면 @WebMvcTest의 기능도 모두 포함된 테스트라고 해석될 수 있다.

 

  • @SpringBootTest를 통해 어플리케이션 내부의 모든 Bean을 등록
  • Mock테스트를 위해 @AutoConfigureMockMvc를 사용 
    • 이로써 MockMvc 객체를 통해 실제 컨테이너가 실행되는 것은 아니지만 로직상 테스트 가능
    • @WebMvcTest가 아닌 환경에서도 Mock 테스트가 가능
  • @ActiveProfiles를 통해 Alpha, Beta가 아닌 test 환경을 사용
  • @MockBean을 사용해 2개의 객체를 Bean으로 등록

 

 

@WebMvcTest

 

1) 특징

- MVC를 위한 테스트. 컨트롤러가 예상대로 작동되는지 테스트하기 위해 사용됨

- Web Layer만 로드하며, @WebMvcTest 어노테이션을 사용시 아래의 항목들만 스캔하도록 제한하여 보다 빠르고 가벼운 테스트가 가능

(ex : @Controller, @ControllerAdvice, @JsonComponent, @Convert, @GenericConverter, Filter, WebMvcConfigurer, HandlerMethodArgumentResolver)

- Security, Filter, Interceptor, request / response Handling, Controller의 항목들만 스캔하도록 제한하며, @Component는 스캔 대상에서 제외

 

2) 장점

- WebApplication과 관련된 Bean들만 등록하기 때문에 @SpringBootTest보다 빠름

- 통합테스트를 진행하기 어려운 테스트를 개별적으로 진행 가능

(ex : 결제 모듈 API 사용에서 특정 조건에 따라 실패하는 경우를 Mock을 통해 가짜 객체를 만들어 테스트 가능)

 

3) 단점

- Mock을 기반으로 테스트 하기 때문에, 실제 환경에서는 예상 밖의 동작오류가 발생할 수 있음

 


 

[ 사용 예시 ]

@WebMvcTest(UserVehicleController.class)
public class MyControllerTests {

	@Autowired
	private MockMvc mvc;

	@MockBean
	private UserVehicleService userVehicleService;

	@Test
	public void testExample() throws Exception {
		given(this.userVehicleService.getVehicleDetails("sboot"))
				.willReturn(new VehicleDetails("Honda", "Civic"));
		this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
				.andExpect(status().isOk()).andExpect(content().string("Honda Civic"));
	}

}

위 코드는 TDD(Test-Driven Development)가 아닌 BDD(Behavior-Driven Development)로 동작하기 때문에 when().then()이 아닌 given().willReturn()이 사용되었다.

 

 

 

참고 자료

 

- 갓대희의 작은 공간 : @SpringBootTest로 통합 테스트 하기

 

[스프링부트 (9)] SpringBoot Test(2) - @SpringBootTest로 통합테스트 하기

[스프링부트 (9)] SpringBoot Test(2) - @SpringBootTest로 통합테스트 하기 안녕하세요. 갓대희 입니다. 이번 포스팅은 [ 스프링 부트 통합 테스트 하기 (@SpringBootTest)] 입니다. : ) 0. 들어가기 앞서 이..

goddaehee.tistory.com

 

- 스프링 공식 사이드 : Testing

 

46. Testing

A Spring Boot application is a Spring ApplicationContext, so nothing very special has to be done to test it beyond what you would normally do with a vanilla Spring context. By default, @SpringBootTest will not start a server. You can use the webEnvironment

docs.spring.io