MockMVC를 이용한 BoardService 단위 테스트 (JUnit5)
참고하는 자료마다 단위 테스트를 진행하는 방법이 조금씩 달랐다.
직접 테스트를 진행해보고, Mock 객체를 이용한 단위테스트를 하는 방법을 터득해보려 한다.
https://itmore.tistory.com/entry/MockMvc-%EC%83%81%EC%84%B8%EC%84%A4%EB%AA%85
MockMVC란?
실제 객체와 비슷하지만, 테스트에 필요한 기능만가지는 가짜 객체를 만들어서 애플리케이션 서버에 배포하지 않고도 스프링 MVC 동작을 재현할 수 있는 클래스를 의미한다.
왜 Mock을 사용하나요?
기존 SpringBootTest를 이용한 통합 테스트는 테스트 단위가 너무 크다. 모든 Bean을 올리고 진행하기 때문에 오래 걸리는 단점이 있고, DB에도 직접 접근하며 많은 코드를 테스트하기 때문에 어디서 에러가 발생하는지 확인하기 쉽지 않다.
Mock을 사용해서, 진행하고자 하는 테스트에만 집중하며 실제 DB에 insert되는지 확인할 필요 없이 빠르게 테스트할 수 있다.
BoardServiceTest에 대한 단위 테스트를 진행, 즉 BoardRepository 를 사용하므로 @Mock 어노테이션으로 가짜 BoardRepository 를 만들어준다.
그리고 @injectMocks를 통해 BoardService에 이 가짜 Mock 객체를 주입해준다.
https://mangkyu.tistory.com/145
@ExtendWith(MockitoExtension.class)
public class BoardServiceTest {
@Mock
NormalBoardRepository boardRepository;
@InjectMocks
BoardService boardService;
private MockMvc mockMvc;
@BeforeEach
public void init(){
mockMvc = MockMvcBuilders.standaloneSetup(boardService).build();
}
@ExtendWith(MockitoExtension.class)
: Junit5를 지원함에 따라, Mockito와 Junit 테스트를 결합하기 위해 Test 클래스 위에 다음과 같은 어노테이션을 붙여준다.
standaloneSetup() : 수동으로 생성하고 구성한 컨트롤러 한 개이상을 서비스할 mock MVC를 만든다.
MockMvcBuilders는 WebappContextSetup() 이라는 메소드도 제공하는데, standaloneSetup 과 다르게, 컨트롤러 뿐만 아니라 의존성까지 로드해 테스트한다.
의문점..
https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
when, given, thenReturn 등의 Mock 주요 함수들을 import 해 사용하려고 했는데, no suggestion 에러가 떴다.
사용하기 이전에 이 메소드들의 용도를 알아보았다 .
바로 Stub이라는 개념을 구현한 것인데, Stub이란 dummy 객체가 마치 실제로 동작하는 것처럼 보이도록 만든 것이다.
import org.mockito.stubbing.OngoingStubbing;
찾아보니까 org.mokito.Mokito에 있음..
이렇게 따로 import 하니까 됨 !!
Alt + Enter 로 import static 추가해주면 when() 사용 가능
when() thenReturn : mock 되기 전에 실제 메소드를 호출하고, 결괏값이 기대값을 반환해야한다고 지정
doReturn() when() : 실제 메소드를 호출하지 않고, 결괏값도 기대값이 반환해야 한다고 지정
일단 따라치고 보는 나...
given 쪽에서 어떻게 save에 대한 Stub을 만들어낼지 고민을 해봐야하는 시점이었다.
any()를 써서 save함수의 매개변수에 넣는 이유?
Body에 저장된 데이터가 MessageConverter를 거쳐 새로운 객체로 변환된다. 따라서 우리는 API에서 전달된 파라미터엔 NormalBoard.class를 사용할 수 없을 수 있다. 따라서 NormalBoard 클래스의 어떠한 객체도 사용할 수 있도록 any() 가 사용되었다.
when(boardRepository.save(any())).thenReturn(saveBoardDto)
이렇게 작성했었는데 어떤 뜻이냐면
boardRepository의 save 메서드를 호출하면, 즉시 saveBoardDto를 반환하도록 한다.
근데 여기서 내가 만든 save 함수는 Detail View인 BoardDto를 반환하도록 설계하였어서, then Return 함수의 매개변수에 saveBoardDto (즉 저장할 때 사용한 Dto)가 아니라, 새로운 Dto를 담아야하는 문제가 생겼다.
음 그래서 그냥 반환하는 객체를 임의로 생성해서 쓰기로 하였다.
boardRepository.save() 뿐만 아니라,
내가 테스트하려는 boardService의 write함수 내부에 얽혀있는 모든 DB접근 메소드를 Mocking 해야한다는 것도 알았다.
여기서 에러가 떴었는데, 내가 Service단에서 em을 사옹해 직접 category 객체를 불러오는 코드를 짰었다.
이때 em이 null로 뜨는 문제가 있었어서, 왜 그런지 한참을 생각했다.
그 끝에 알게된 것은,, 의존성 주입이 안되어서였다.
이렇게 의존성 주입해서 해결!