Mockito 를 사용한 단위테스트

알파로그·2023년 10월 22일
0

🔗 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;
		// 모든 PostService 객체가 전부 의존
    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() {

		// given
    Long
            memberId1 = 1L,
            memberId2 = 2L,
            postId = 1L,
            commentId = 1L;
    String content = "modify content";
    Post post = createPost(memberId1, postId);
    Comment comment = createComment(memberId2, commentId, post);

		// when
    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("수정 권한이 없습니다.");
}
profile
잘못된 내용 PR 환영

0개의 댓글