본문 바로가기
공부/프로그래밍

[springboot] 데이터 사용 Service를 mockito로 테스트하기

by demonic_ 2020. 2. 12.
반응형

mockito는 단위 테스트를 위한 java mocking framework이다.

 

어디다 쓰냐면... 단위 테스트를 해야 하는데 데이터베이스에서 데이터를 가져와야 할 경우 테스트 환경에 따라 각각 다른 데이터가 조회될 수 있는데 예를 들어 A라는 고객이 1번_테스트_데이터베이스에서는 ID가 1인데 2번_테스트_데이터베이스에서는 5번일 수 있다. 이럴 경우 상황에 따라선 테스트의 단위가 깨지게 되는데, mockito를 이용하면 입력한 값을 리턴하게 되므로 동일한 환경으로 테스트할 수 있게 된다.

 

 

mockito를 사용하려면 dependencies를 추가해야 하지만 SpringBoot를 사용하면 gradle에 spring-boot-starter-test를 추가하게 되는데, 그 안에 mockito-junit-jupiter 패키지가 포함되어 있다.

 

 

만약 springboot를 사용하지 않는다면 다음 패키지를 추가하면 된다

dependencies {
    ....
    testCompile group: 'org.mockito', name: 'mockito-junit-jupiter', version: '3.1.0'
    ....
}

 

 

mock에는 2개의 어노테이션이 있는데 @Mock 과 @InjectMocks 가 있다. 둘의 차이는 다음과 같다

@Mock: mock 객체를 만들어 반환

@InjectMocks: @Mock이나 @Spy 객체를 자신의 멤버 클래스와 일치하면 주입

 

@InjectMocks를 사용하는 경우를 예를 들면 MemberService 안에 MemberRepository 가 있을 때

MemberRepository가 주입받고 난 후에 MemberService 가 생성되어야 하는데 이때 사용에 용이하다.

 

간단하게 코드로 보면 다음과 같다

# MemberService 클래스
@Service
@RequiredArgsConstructor
public class MemberService {

    private final MemberRepository memberRepository;

    public List<Member> findMemberAll() {
        return memberRepository.findAll();
    }
}
# MemberRepository 클래스
@Repository
public class MemberRepository {

    @PersistenceContext
    private EntityManager em;


    public void persist(Member member) {
        em.persist(member);
    }

    public List<Member> findAll() {
        return em.createQuery("select m from Member m", Member.class)
                .getResultList();
    }
}

 

MemberService의 findMemberAll() 메서드를 테스트할 경우

@ExtendWith(MockitoExtension.class)
public class MemberServiceMockTest {
    @Mock
    private MemberRepository memberRepository;

    @InjectMocks
    private MemberService memberService;

    ...
}

 

 

그럼 이제 사용법에 대해 알아보자.

 

우선 Repository만 테스트하는 것을 만들어본다

given 을 이용해 memberRepository.findAll() 을 조회할 경우 리턴할 값을 지정해둔다.

이후 기능을 수행하도록 코드를 짠 뒤에 리턴된 결괏값을 비교하여 테스트가 성공하는지 확인한다.

...
    @Mock
    private MemberRepository memberRepository;

    @Test
    @DisplayName("mockito 레파지토리 테스트")
    void mockMemberRepositoryTest() {
        //given
        Member member1 = Member.builder()
                .name("mock유저1")
                .age(20)
                .build();
        List<Member> members = new ArrayList<>();
        members.add(member1);

        given(memberRepository.findAll()).willReturn(members);

        //when
        List<Member> findMembers = memberRepository.findAll();
        System.out.println("findMembers = " + findMembers);

        //then
        Assertions.assertEquals(1, findMembers.size());
        Assertions.assertEquals(member1.getName(), findMembers.get(0).getName());
    }
...

 

수행하면 테스트는 성공하고 다음의 내용을 콘솔에 보여준다

findMembers = [Member(id=null, name=mock유저1, age=20)]

 

그럼 이번에 Service를 테스트해보자

...
    @Mock
    private MemberRepository memberRepository;

    @InjectMocks
    private MemberService memberService;

    @Test
    @DisplayName("mockito 서비스 테스트")
    void mockMemberServiceTest() {
        //given
        Member member1 = Member.builder()
                .name("mock유저1")
                .age(20)
                .build();
        List<Member> members = new ArrayList<>();
        members.add(member1);

        given(memberRepository.findAll()).willReturn(members);

        //when
        List<Member> findMembers = memberService.findMemberAll();

        System.out.println("memberAll = " + findMembers);


        //then
        Assertions.assertEquals(member1.getName(), findMembers.get(0).getName());
    }
...

 

테스트는 성공으로 되고 다음의 내용을 콘솔로 보여준다

memberAll = [Member(id=null, name=mock유저1, age=20)]

 

 

기타)

@InjectMocks를 일반 @Mock으로 변경해 테스트하면 어떻게 될지 확인해봤다.

다음과 같은 에러가 발생한다

java.lang.IndexOutOfBoundsException: Index: 0, Size: 0

	at java.base/java.util.LinkedList.checkElementIndex(LinkedList.java:559)
	at java.base/java.util.LinkedList.get(LinkedList.java:480)
    at kr.sample.demo.service.MemberServiceMockTest.mockMemberServiceTest(MemberServiceMockTest.java:72)
    ...
	Suppressed: org.mockito.exceptions.misusing.UnnecessaryStubbingException: 
Unnecessary stubbings detected.
Clean & maintainable test code requires zero unnecessary code.
Following stubbings are unnecessary (click to navigate to relevant line of code):
  1. -> at kr.sample.demo.service.MemberServiceMockTest.mockMemberServiceTest(MemberServiceMockTest.java:63)
Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class.

 

 

Unnecessary stubbings detected.를 표기하며 에러가 발생한다.

해당 에러가 발생한 곳은 given() 구절로, 불필요한 stubbings 을 한 것이 원인이라 표시되어 있다.

현 테스트에 불필요한 코드가 들어있다는 의민데, 해당 옵션을 끄려면 다음 설정을 하면 된다. 

@MockitoSettings(strictness = Strictness.LENIENT)
class ...

그런데 왠만하면 끄지말고 진행하는 것을 추천한다.

 

 

끝.

 

 

 

 

샘플은 아래 github에서 확인할 수 있다.

https://github.com/lemontia/mockitoTest

 

lemontia/mockitoTest

Contribute to lemontia/mockitoTest development by creating an account on GitHub.

github.com

 

반응형

댓글