먼저 Controller 계층에 관한 이야기를 해보겠다.
Controller 계층에 관한 이야기를 하기 전에 spring에서는 계층을 왜 나누는 걸까? 코드의 구조화, 유지 보수성, 테스트 용이성, 확장성, 재사용성 등 많은 이유가 있지만 주된 이유는 계층 간의 분리를 통해 코드를 구조화 시켜 SOLID 원칙을 적용시킬 수 있음에 중점을 둔다.
너무 허황된 이야기일 수 있고 와닿지 않을 수 있다. 그렇다면 좀 더 현실적으로 이야기 해보자. Controller와 Service 계층을 나누는 이유는 위의 이유도 있겠지만 다음과 같은 이유도 존재한다. Controller의 주된 역할은 입력과 출력 즉, 통신이다. client에게 데이터를 받고 service에게 전달 후 가공된 데이터를 응답하는 징검다리 역할을 한다. 만약 Service가 없고 Controller가 비즈니스 로직을 수행한다면 로직 자체의 에러, thread pool은 한정적인데 이를 넘어서는 방대한 양의 요청 등이 오면 Controller가 다운될 수 밖에 없다. 이를 방지하기 위해서 비즈니스 로직을 따로 분리해 책임을 전가하는데 이때 분리한 클래스를 Service라고 부른다.
Controller를 테스트하기 위해서도 Controller 자체에 대한 테스트를 진행해야지 그와 관련된 service, 그리고 serive와 관련된 repo에 관해서도 테스트를 해버리면 구조가 복잡해지고 테스트 코드 작성의 의미가 희석된다. 결과적으로 전체 테스트를 진행하는 것과 같다.
이를 방지하기 위해 spring에서 @WebMvcTest 와 @AutoConfigureMockMVC 를 만들었는데 차이점은 다음과 같다.
@WebMvcTest @AutoConfigureMockMvc
| 공통점 | 컨트롤러를 테스트할 때 사용하는 어노테이션 | 컨트롤러를 테스트할 때 사용하는 어노테이션 |
| 차이점 | - @Controller, @ControllerAdvice 등 사용 가능(@Service, @Repository 사용불가) - @MockBean을 사용하여 컨트롤러의 협력체 들을 생성 - 주로 간단한 테스트에서 사용 |
- 컨트롤러 뿐 아니라 @Service, @Repository 들도 모두 메모리에 올려서 사용 - @AutoConfigureMockMvc와 @SpringBootTest를 결합하여 사용 - 주로 세밀하게 제어할 경우 사용 |
나는 @WebMvcTest 와 @MockBean 을 사용해서 테스트를 진행해봤다.

이건 Controller의 부분 메서드이다. 목적은 client에게 상품의 정보를 전달하는 것이다. 위의 코드를 간단히 설명하자면, productService가 상품을 조회해서 가지고 와 DTO로 변환해 전달하는 코드이다.(DTO 변환에 관해서도 작성할 점이 많지만 주제에 맞지 않는 것 같아 다음 기회에 작성하겠다.)
돌아와서 Controller 테스트 코드를 작성함에 있어서 위의 productService가 수행하는 일까지 확인을 할 필요가 있을까? 그러면 좋겠지만 굉장히 세밀한 작업과 복잡한 코드 또 잘못하면 테스트 코드 작성의 의미가 희석될 수 있는 문제점이 발생한다. 이를 위해서 Mockito를 사용한다. 아래의 코드는 ControllerTest 코드이다.

부가적으로 테스트 코드는 의존성 주입이 자동적으로 안되기 때문에 @Autowired 로 수동 주입해줘야 한다. 주입 방법에는 3가지가 있지만 spring에서는 불변 객체, 순환 참조 문제 발생 등으로 생성자 주입을 권장하고 있다. 또한 productService를 @MockBean 으로 등록했다.

위에서도 말했듯이 Controller 테스트에서 굳이 Service에 관한 기능을 수행할 필요는 없다고 생각한다. 따라서 우리가 직접 전달할 내용을 작성해 전달함으로써 계층 간의 분리가 이루어진다.

위의 코드가 Mockito를 사용한 부분이다. “productService.findAllDTO(*any*()) 의 코드를 만나면 매개변수로 어떤 것이 있어도 다음과 같은 코드를 리턴해라.” 라는 의미라고 볼 수 있다.
given().willReturn() 말고도 when().thenReturn() 도 있지만 같은 기능이지만 다른 클래스라고 보면 된다.
그리고 Mockmvc를 통해 Controller의 findAll 메서드를 실행하는데 실행 중에 productService.findAllDTO(productService.findAllProduct(page)) 코드를 만나면 약속했던 값을 반환하고 그대로 진행해서 반환 값을 도출한다. 이렇게 반환 받은 값을 테스트함으로써 Controller 테스트 코드가 완성이 된다.
마지막으로 시작하는 말에서 언급했던 부분인데, Controller 테스트는 DB에 있는 모든 데이터를 잘 가져왔는지에 대한 테스트가 아닌 요청을 잘 받고 응답을 잘 하는지에 관한 테스트이기 때문에 테스트 데이터를 무자비하게 많이 가져올 필요가 없다. DB에 있는 모든 데이터에 관한 테스트는 RepositoryTest에서 수행해야 할 일이다. 이렇게 분리를 함으로써 테스트를 용이하게 할 수 있다.