🔗 Mockito 환경설정
✏️ 테스트 객체 세팅
📍 목표
- 게시물을 생성하는 객체인
PostCreateService
의 게시물 생성 메서드인 write
를 검증할 계획인다.
- 코드를 살펴보면 DB 에 저장하기 위해 repository 를 의존하고,
연관관계에 있는 객체를 생성하기 위해 다른 CreateUseCase 도 의존하고있다.
@Service
@Transactional
@RequiredArgsConstructor
public class PostCreateService implements PostCreateUseCase {
private final CodeReviewCreateUseCase codeReviewCreateUseCase;
private final PostRepositoryPort repository;
@Override
public CodeReviewDto write(Long memberId, CreateCodeReviewDto dto) {
Post post = repository.save(
Post.write(memberId, dto)
);
codeReviewCreateUseCase.write(post, dto);
return new CodeReviewDto(post, dto.getProblemStatusId());
}
}
📍 기본 세팅
- 단위테스트는 객체에
@ExtendWith(MockitoExtension.class)
를 선언해주면 된다.
- 순수한 비즈니스 로직을 검증하기 위해
@InjectMocks
를 사용해 서비스 객체를 주입받는다.
@BeforeEach
를 선언해 의존중인 객체에 대한 모킹을 생성한다.
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
@DisplayName("게시글 작성")
@ExtendWith(MockitoExtension.class)
class PostCreateService_writeTest {
@InjectMocks
PostCreateService createService;
@BeforeEach
void setup() {
}
}
✏️ 목킹과 검증의 분리
- 지금 내가 단위테스트 하려는 객체는 2개의 의존성을 갖고있다.
- 이중 repository 는 모든 PostService 가 공통으로 의존하고 있고,
나머지 하나는 오직 지금 객체만 의존하고있다.
@Service
@Transactional
@RequiredArgsConstructor
public class PostCreateService implements PostCreateUseCase {
private final CodeReviewCreateUseCase codeReviewCreateUseCase;
private final PostRepositoryPort repository;
📍 목킹 객체 분리
- 먼저 현재 객체만 의존하고 있는 객체에 대한 Mocking 용 객체를 생성해준다.
- PostCreateService 에서만 사용되므로 PostCreateMock 이라고 명명했다.
- write method 호출이 발생했을 때 아무것도 하지않길 원하므로
doNothing()
으로 모킹 로직을 만들었다.
- method 를 public 으로 선언해줘야 Test 객체에서 호출이 가능하다.
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
public class PostCreateMock {
private CodeReviewCreateUseCase codeReviewCreateUseCase =
Mockito.mock(CodeReviewCreateUseCase.class);
public void writeCodeReviewMocking() {
doNothing()
.when(codeReviewCreateUseCase)
.write(any(), any());
}
}
- 다음은 Repository 목킹객체를 만들차례이다.
- 이렇게 두개로 나누는 이유는 중복을 없애고 재사용성을 높이기 위해서다.
- 실제로는 Post 객체를 repository.save() 의 파라미터로 입력하면 DB 에 저장되어 id 값이 추가되 Post 객체로 반환되지만,
순수 서비스 로직 검증이 목표기 때문에 파라미터를 그대로 반환하도록 목킹을 해줬다.
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
public class PostMock {
private PostRepositoryPort repository =
Mockito.mock(PostRepositoryPort.class);
public void savePostMocking() {
when(repository.save(any()))
.thenAnswer(invocation -> {
Post post = (Post) invocation.getArgument(0);
return post;
});
}
}
📍 검증 객체 작성
- 이제 각 객체를 순서대로 상속받고 BeforeEach 에서 메서드를 호출해주면 된다.
- 이렇게 하면 깔끔하게 목킹 코드와 검증코드를 분리할 수 있고,
가독성이 높아저 유지 보수성이 좋아진다.
public class PostCreateMock extends PostMock {
@DisplayName("게시글 작성")
@ExtendWith(MockitoExtension.class)
class PostCreateService_writeTest extends PostCreateMock {
@InjectMocks
PostCreateService createService;
@BeforeEach
void setup() {
writeCodeReviewMocking();
savePostMocking();
}
✏️ 단위 테스트 작성
📍 성공할 경우
- 아무 예외가 발생하지 않고 검증 로직이 완료되면 성공이다.
@Test
@DisplayName("댓글 수정 성공")
void no1() {
Long
memberId1 = 1L,
memberId2 = 2L,
postId = 1L,
commentId = 1L;
String content = "modify content";
Post post = createPost(memberId1, postId);
Comment comment = createComment(memberId2, commentId, post);
modifyService.comment(memberId2, comment, content);
}
📍 실패할 경우
- 이번엔 일부러 권한이 없는 member id 를 사용해 예외가 잘 밸상하는지에 대한 검증이다.
@Test
@DisplayName("수정 권한이 없는 경우")
void no2() {
Long
memberId1 = 1L,
memberId2 = 2L,
postId = 1L,
commentId = 1L;
String content = "modify content";
Post post = createPost(memberId1, postId);
Comment comment = createComment(memberId2, commentId, post);
assertThatThrownBy(() -> modifyService.comment(memberId1, comment, content))
.isInstanceOf(NoPermissionException.class)
.hasMessageContaining("수정 권한이 없습니다.");
}