Pull Request 007 - 자유게시글 작성 기능 TDD로 구현해보기 - @SpringBootTest 제거하기
이전에 작성했던 테스트에서는 Service 계층을 테스트하기 위해 @SpringBootTest 애너테이션을 사용했다. @SpringBootTest애너테이션을 사용해도 우리가 원하는 테스트는 가능하다. 필요한 Bean들이 Spring Container에 생성되어 적절히 주입되고, 기능을 올바르게 테스트 해볼 수 있다. 그러나 Bean이 생성된다는 데에서 문제가 생긴다.
테스트는 F.I.R.S.T원칙을 지키도록 작성해야 한다. 그러나 Bean을 모두 생성하는 것은 첫 번째 원칙인 Fast에 어긋난다. Service 계층만 테스트하고 싶은데, 불필요하게 Repository나 Controller계층의 객체들까지 생성하기 때문에 당연히 시간이 오래 걸린다. 따라서 @SpringBootTest 애너테이션을 제거하고, 대신 Mockito를 사용해보자.
기존에 작성했던 테스트 코드는 다음과 같다.
@SpringBootTest
class FreeBoardCommandServiceTest {
@Autowired
private FreeBoardCommandService freeBoardCommandService;
@Test
void saveTest(){
FreeBoard testEntity = FreeBoard.builder()
.title("test")
.content("content")
.build();
FreeBoard savedEntity = freeBoardCommandService.save(testEntity);
assertThat(savedEntity.getId()).isEqualTo(1L);
assertThat(savedEntity.getTitle()).isEqualTo(testEntity.getTitle());
assertThat(savedEntity.getContent()).isEqualTo(testEntity.getContent());
}
}
여기서 @SpringBootTest 애너테이션을 제거하면, @Autowired 애너테이션도 사용할 수 없다. 당연하다, Bean이 생성되지 않고 Spring Container도 생성되지 않기 때문에 당연히 필요한 의존관계가 주입되지 않는다. 위의 코드를 Mockito를 사용하는 코드로 수정하면 아래와 같이 수정된다.
@ExtendWith(MockitoExtension.class)
class FreeBoardCommandServiceTest {
@Mock
private FreeBoardRepository freeBoardRepository;
@InjectMocks
private FreeBoardCommandService freeBoardCommandService;
@Test
void saveTest() {
FreeBoard testEntity = FreeBoard.builder()
.title("test")
.content("content")
.build();
FreeBoard savedEntity = FreeBoard.builder()
.title("test")
.content("content")
.build();
savedEntity.setId(1L);
given(freeBoardRepository.save(any())).willReturn(savedEntity);
savedEntity = freeBoardCommandService.save(testEntity);
assertThat(savedEntity.getId()).isEqualTo(1L);
assertThat(savedEntity.getTitle()).isEqualTo(testEntity.getTitle());
assertThat(savedEntity.getContent()).isEqualTo(testEntity.getContent());
}
}
@SpringBootTest 대신 @ExtendWith(MockitoExtension.class) 애너테이션이 사용되었다. 해당 애너테이션을 통해 주어진 테스트 클래스가 Mockito를 사용할 것임을 명시한다. @Autowired 대신, @InjectMocks와 @Mock 애너테이션이 붙었다. @Mock의 경우에는 가짜 클래스를 만든다는 것이다. @InjectMocks 는 이렇게 가짜로 만들어진 객체를 집어넣어 객체를 만든다는 뜻이다.
이렇게 작성하고 나니 뭔가 좀 이상한 것 같다. savedEntity라는 인스턴스를 처음에 만들 필요가 없다. 수정해보자.
@Test
void saveTest() {
FreeBoard testEntity = FreeBoard.builder()
.title("test")
.content("content")
.build();
testEntity.setId(1L);
given(freeBoardRepository.save(any())).willReturn(testEntity);
FreeBoard savedEntity = freeBoardCommandService.save(testEntity);
assertThat(savedEntity.getId()).isEqualTo(1L);
assertThat(savedEntity.getTitle()).isEqualTo(testEntity.getTitle());
assertThat(savedEntity.getContent()).isEqualTo(testEntity.getContent());
}
이렇게 @SpringBootTest 애너테이션을 지우고 Mockito를 적용함으로써 작은 단위 테스트였지만, 실제 테스트 시간이 조금 짧아졌다.불필요한 시간 사용을 줄일 수 있게 된 것이다. 만일 테스트가 많아진다면, 당연히 시간 단축이 더욱 많이 될 것이다.
물론 이렇게 진행한 테스트는 레파지토리에 대한 테스트는 전혀 되지 않는다. 가짜 결과값을 반환값으로 지정해놨는데, 테스트가 어떻게 되겠는가. 다만 우리가 테스트해보고자 하는 점은 정확하게 '원하는 결과값이 반환되었는가?'를 테스트하고 싶은 것이다. 따라서 레파지토리에 대한 테스트는 다시 진행해야 한다.