본 시리즈는 메타 코딩님의 Junit 강의를 학습한 내용을 바탕으로 정리하였습니다.
저번 포스팅에서 Service
레이어만 독자적으로 테스트하기 위해 Mock
라이브러리를 통해 테스트를 구현한다고 했었다. 실제로 구현해보자.
BookServiceTest.java
package site.metacoding.junitproject.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import site.metacoding.junitproject.domain.BookRepository;
import site.metacoding.junitproject.util.MailSender;
import site.metacoding.junitproject.util.MailSenderStub;
import site.metacoding.junitproject.web.dto.BookRespDto;
import site.metacoding.junitproject.web.dto.BookSaveReqDto;
@ExtendWith(MockitoExtension.class) // 1.
public class BookServiceTest {
@InjectMocks // 2.
private BookService bookService;
@Mock // 3.
private BookRepository bookRepository;
@Mock
private MailSender mailSender;
@Test
public void 책등록하기_테스트() {
// given
BookSaveReqDto dto = new BookSaveReqDto();
dto.setTitle("Junit 강의");
dto.setAuthor("메타코딩");
// stub
// when
BookRespDto bookRespDto = bookService.책등록하기(dto);
// then
assertEquals(dto.getTitle(), bookRespDto.getTitle());
assertEquals(dto.getAuthor(), bookRespDto.getAuthor());
}
}
MockitoExtension
메서드를 이용해 BookServiceTest
를 가짜 메모리 환경으로 만든다. @InjectMocks
를 붙여준다.@Mock
어노테이션을 붙여준다.그럼 이제 테스트를 돌려보면...
😱...!
위와 같이 NullPointerException
이 터지는 것을 볼 수 있다. 왜 Null 값이 리턴되는 예외가 발생할까? 이는 조금만 생각해보면 당연한 일이다.
우리는 @InjectMocks
을 통해 service 레이어를 Mock으로 가져왔다. 근데 그 뿐이지 Mocking 을 통해 어떤 것을 수행할지는 정의해주지 않은 것이다.
따라서 우리는 행동을 정의해줄 필요성이 있다. 우리에게는 stub(가정)
이 있다. 이렇게 작성해보자.
Stub
정의BookService.java
@Test
public void 책등록하기_테스트() {
// given
BookSaveReqDto dto = new BookSaveReqDto();
dto.setTitle("Junit 강의");
dto.setAuthor("메타코딩");
// stub (가설)
when(bookRepository.save(any())).thenReturn(dto.toEntity()); // 1.
when(mailSender.send()).thenReturn(true); // 2.
// when
BookRespDto bookRespDto = bookService.책등록하기(dto);
}
}
우리의 Stub
은 다음을 상정하고 있다.
bookRepository
즉, 실제 DB에 저장되는 어떤 값이든(any) 실제 책 등록이 일어날 때, 그것은 BookService.java
의 값이 저장되어 리턴되는 것임을 의미한다.mailSender
클래스에서 send 메소드가 실행되는 것은 mailSender
의 boolean 값 true가 return 되는 것과 같음을 의미한다.위의 1번이 뭘 말하는 것일까?
실제, BookService.java
의 코드를 잠깐 살펴보면...
// 1. 책 등록
@Transactional(rollbackOn = RuntimeException.class)
public BookRespDto 책등록하기(BookSaveReqDto dto) {
> Book bookPS = bookRepository.save(dto.toEntity());
if (bookPS != null) {
// (...생략)
}
초록색 화살표의 코드 라인을 보면 실제 bookService
에서 save
를 진행할 때, 우리는 bookRepository
를 Mock
해놓았기 때문에(가짜로 만들었기 때문에) 실제 값이 들어갈 수가 없다.
그렇다고 테스트를 못한다면 안되는 일.. bookRepository
에 어떤 값이 들어가든지간에 리턴되는 값은 bookPS
의 dto
값이다라고 테스트를 작성한 것이다.
이로써 우리는 가짜환경을 구축하고 그 환경 위에서 테스트를 진행할 수 있게 되었다.
이에 대한 검증은 지금까지 우리가 쓰던 assertEquals
보다 더 가독성이 좋고 여러 기능들이 포함된 AssertJ
라이브러리의 assertThat
을 사용해서 검증한다.
// (..생략)
// then
// assertEquals(dto.getTitle(), bookRespDto.getTitle());
// assertEquals(dto.getAuthor(), bookRespDto.getAuthor());
assertThat(dto.getTitle()).isEqualTo(bookRespDto.getTitle());
assertThat(dto.getAuthor()).isEqualTo(bookRespDto.getAuthor());
}
}
📌 참고
assertj 라이브러리를 사용하기 위해선build.gradle
설정 파일에testImplementation("org.assertj:assertj-core:3.23.1")
을 추가해주고 반영해야한다.
Assertions
사용을 위해선 필수 라이브러리인AssertJ
의 특징과 여러 기능들이 궁금하다면❓
daleseo님의 블로그 (https://www.daleseo.com/assertj/)