[질문] Mockito - JPA Specification 단위테스트

2017-03-17 19:15

Mockito를 가지고 JPA repository 단위테스트 작성중인데.. 풀리지 않는 의문점이 있어 질문드려봅니다. Specification 객체에 대해 mock을 주입하는 방식으로 테스트를 작성하고 있는데 의도한대로 동작하질 않네요.. repository.count 메소드 인자로 Specification 객체를 전달하는 루틴인데 의도대로면 true를 리턴해줘야하는데 의도대로 안되네요

Specifications.where(MemberSpecification.nickEqual("뽀로로")))

전체 소스코드

package net.ncrash.api.member.query.application;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;

import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.data.jpa.domain.Specifications;

import net.ncrash.api.member.query.specification.MemberSpecification;
import net.ncrash.member.repository.DormantMemberRepository;
import net.ncrash.member.repository.MemberJpaRepository;


/**
 * Class
 *
 * @author David
 * @version 1.0
 * @created 2017. 03. 17. 18:54
 * @history
 */
public class MemberQueryServiceTest {
  @InjectMocks
  private MemberQueryService memberQueryService;

  @Mock
  private MemberJpaRepository memberRepository;

  @Mock
  private DormantMemberRepository dormantMemberRepository;

  @Before
  public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
  }

  @Test
  public void duplicateNickname() throws Exception {
    // given

    // when
    when(memberRepository.count(Specifications.where(MemberSpecification.nickEqual("뽀로로")))).thenReturn(1L);

    // then
    assertThat(memberQueryService.duplicateNickname("뽀로로")).isTrue();
  }
}
package net.ncrash.api.member.query.application;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specifications;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import net.ncrash.api.member.query.specification.DormantMemberSpecification;
import net.ncrash.api.member.query.specification.MemberSpecification;
import net.ncrash.member.repository.DormantMemberRepository;
import net.ncrash.member.repository.MemberJpaRepository;

/**
 * Class
 *
 * @author David
 * @version 1.0
 * @created 2017. 03. 17. 18:46
 * @history
 */
@Service
@Transactional(readOnly = true)
public class MemberQueryService {
  private MemberJpaRepository memberRepository;
  private DormantMemberRepository dormantMemberRepository;

  @Autowired
  public MemberQueryService(MemberJpaRepository memberRepository,
      DormantMemberRepository dormantMemberRepository) {
    this.memberRepository = memberRepository;
    this.dormantMemberRepository = dormantMemberRepository;
  }

  /**
   * 회원 중복 닉네임 조회
   *
   * @param nick 회원 닉네임
   * @return
   */
  public boolean duplicateNickname(final String nick) {
    if (StringUtils.isEmpty(nick)) {
      return false;
    } else {
      final long memberCount =
              memberRepository.count(Specifications.where(MemberSpecification.nickEqual(nick)));
      final long dormantMemberCount = dormantMemberRepository
              .count(Specifications.where(DormantMemberSpecification.nickEqual(nick)));

      return memberCount > 0L || dormantMemberCount > 0L;
    }
  }
}
package net.ncrash.api.member.query.specification;

import org.springframework.data.jpa.domain.Specification;

import net.ncrash.member.domain.MemberEntity;
import net.ncrash.member.domain.MemberEntity_;
import net.ncrash.member.domain.MemberIdentityValue_;

/**
 * {@link MemberEntity} JPA Specification Class For API
 *
 * @author David
 * @version 1.0
 * @created 2017. 03. 06. 16:18
 * @history
 */
public class MemberSpecification {

  public static Specification<MemberEntity> nickEqual(final String nick) {
    return (root, query, cb) -> cb
        .equal(root.get(MemberEntity_.identity).get(MemberIdentityValue_.nick), nick);
  }
}

5개의 의견 from SLiPP

2017-03-19 12:21

테스트 결과가 false로 나온건지 error나온건지 로그가 어떻게 되나요?

// Specification.class 에는 적절한 클래스나 조건을 수정해주세요
when(memberRepository.count(any(Specification.class))).thenReturn(1L);

위 처럼 해보세요

2017-03-20 09:30

@jhindhal.jhang 답변 감사합니다.

테스트 결과는 false 리턴하고 말씀해주신 방법(any)으로 테스트해본 결과 true 반환해주는걸 확인했습니다. 알려주신 방법으로 테스트를 작성할 때 제약사항이 테스트 메소드에 아래와 복합조건을 테스트 하는게 가능한지 궁금합니다. any로 mock 설정할땐 다양한 인자조건에 따른 return 값 설정이 불가능해 보이네요

assertThat(memberQueryService.duplicateNickname("에디")).isFalse();
assertThat(memberQueryService.duplicateNickname("뽀로로")).isTrue();

위와 같이 인자조건이 달라지는 경우 각기 다른 테스트 메소드로 구성하면 되긴 하는데 이 방법이 최선은 아닌것 같아 더 좋은 해결방법은 없는지 궁금하네요

2017-03-20 09:40

이야기 한데로 가능한데 정확히 when() 부분에 적절한 class를 선언 하지 못한것으로 생각 된다. 결국 테스트 하려는건 MemberSpecification.nickEqual("뽀로로") 위 class인데 Specificatin을 처리하고있어서 그런듯

이야기 대로 인자 조건을 설정하게 when을 선언하고 사용할 수 있을 겁니다.(이건 숙제로 ^^)

그리고 소스 보다가 궁금해서 물어보는건데 duplicateNickname() 함수를 테스트를 위한것 같은데 그럴경우 memberRepository.count() 가 1L을 return하고 그걸 테스트 하는게 맞을 듯 합니다. (단위 테스트의 관점에서는)

2017-03-20 13:33

@jhindhal.jhang 오랜만에 숙제 해보네요 ^^

이야기 한데로 가능한데 정확히 when() 부분에 적절한 class를 선언 하지 못한것으로 생각 된다. 결국 테스트 하려는건 MemberSpecification.nickEqual("뽀로로") 위 class인데 Specificatin을 처리하고있어서 그런듯 이야기 대로 인자 조건을 설정하게 when을 선언하고 사용할 수 있을 겁니다.(이건 숙제로 ^^)

위 요건은 Mockito에서는 Mockito.thenAnswer 사용해서 인자조건에 따른 리턴 매핑이 가능하네요. 참고한 링크는 http://stackoverflow.com/a/22345982/1533966 구요 의도한대로 동작하는데 요구사항 부합여부에 대해선 확신이 부족하네요

private String nickname;

@Test
public void duplicateNickname() throws Exception {
    when(memberRepository.count(any(Specifications.class))).thenAnswer(invocation -> {
      switch (nickname) {
        case "뽀로로":
          return 1L;
        default:
          return 0L;
      }
    });

    // then
    nickname = null;
    assertThat(memberQueryService.duplicateNickname(nickname)).isFalse();

    nickname = "";
    assertThat(memberQueryService.duplicateNickname(nickname)).isFalse();

    nickname = "에디";
    assertThat(memberQueryService.duplicateNickname(nickname)).isFalse();

    nickname = "뽀로로";
    assertThat(memberQueryService.duplicateNickname(nickname)).isTrue();
}

그리고 소스 보다가 궁금해서 물어보는건데 duplicateNickname() 함수를 테스트를 위한것 같은데 그럴경우 memberRepository.count() 가 1L을 return하고 그걸 테스트 하는게 맞을 듯 합니다. (단위 테스트의 관점에서는)

MemberQueryServiceTest는 duplicateNickname 함수 테스트를 위한것 맞습니다. 하여 의존관계인 memberRepository.count() 함수 return 값을 thenAnswer 루틴으로 정의한것입니다.

의견 추가하기

연관태그

← 목록으로