본문 바로가기
Spring

Junit5(Juptier) Service 테스트코드 작성법(Mock, MockBean 차이점 확인)

by devjh 2021. 3. 13.
반응형

서비스코드 테스트작성법과 Mock, MockBean의 차이를 정리합니다.

 

Mock vs MockBean

공통점

  • 둘다 가짜객체이며 테스트스텁의 한 종류입니다.
  • given, when. verify 등을 사용하여 행위를 테스트합니다.

차이점

  • MockBean은 가짜 Bean을 스프링에 등록해달라는 의미입니다.
    • 스프링 컨테이너가 기존에 갖고있는 Bean객체는 MockBean객체로 치환되어 DI에 사용됩니다.
  • Mock은 가짜객체를 만드는데 스프링빈에 등록이 안되는 객체입니다.
    • 스프링 컨테이너가 DI를 하는 방식이 아니라 객체생성시 생성자에 Mock객체를 직접 주입해줍니다.
    • 생성자 주입을 사용해야 편하게 사용 가능합니다
    • 스프링을 띄우지 않으므로 MockBean을 사용할때보다 빠릅니다.

 

1. pom.xml

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-test</artifactId>
	<scope>test</scope>
</dependency>

스프링부트로 프로젝트를 생성하면 기본적으로 해당 어노테이션이 포함됩니다.

해당 의존성은

junit5, AssertJ, Mockito 등의 라이브러리를 포함합니다.

 

2. MemberService.java

package com.example.junittut.service;

import com.example.junittut.model.Member;
import com.example.junittut.repository.MemberMapper;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class MemberService {

    MemberMapper memberMapper;

    public MemberService(MemberMapper memberMapper) {
        this.memberMapper = memberMapper;
    }

    public List<Member> list() {
        return memberMapper.selectAllMembers();
    }

    public int insert(Member member) {
        return memberMapper.insertMember(member);
    }

    public int delete(int id) {
        return memberMapper.deleteMember(id);
    }

    public Member detail(int id) {
        return memberMapper.selectById(id);
    }

    public int update(Member member) {
        return memberMapper.updateMember(member);
    }
}

서비스단의 코드입니다.

생성자주입을 통해 MemberMapper를 주입받고있습니다.

 

 

3. MemberMapper.java

package com.example.junittut.repository;

import com.example.junittut.model.Member;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface MemberMapper {
    List<Member> selectAllMembers();

    Member selectById(int id);

    int insertMember(Member member);

    int deleteMember(int id);

    int updateMember(Member member);
}

매퍼 인터페이스입니다.

 

 

4. 테스트코드(Mock을 사용한 서비스코드 테스트)

package com.example.junittut.service;

import com.example.junittut.model.Member;
import com.example.junittut.repository.MemberMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;

class MemberServiceTest {

    MemberService memberService;

    @Mock
    MemberMapper memberMapper;

    @BeforeEach
    void setup() {
        MockitoAnnotations.initMocks(this);
        memberService = new MemberService(memberMapper);
    }

    @Test
    @DisplayName("멤버 상세조회")
    void detail() {
        int memberId = 1;
        String memberName = "Tom";

        Member mockMember = Member.builder().id(memberId).name(memberName).build();

        given(memberMapper.selectById(memberId)).willReturn(mockMember);

        Member responseMember = memberService.detail(memberId);

        assertThat(responseMember.getName()).isEqualTo(memberName);
    }

    @Test
    @DisplayName("멤버 생성")
    void insert() {
        int memberId = 1;
        String memberName = "Tom";

        Member mockMember = Member.builder().id(memberId).name(memberName).build();

        memberService.insert(mockMember);

        verify(memberMapper).insertMember(mockMember);

    }


}

 

해당 테스트코드는 @SpringBootTest 어노테이션이 붙지 않았습니다. 직접 객체를 생성하여 행위를 테스트해줘야합니다. 스프링을 띄우지 않으므로 스프링 컨테이너를 이용하지 못하므로 서비스객체도 생성하고 의존성도 직접 주입해줘야합니다.

이때 Mock 객체를 사용합니다.

 

@BeforeEach

  • @Test 어노테이션이 붙은 메서드들이 실행되기 전에 실행되는 메서드입니다.
 memberService = new MemberService(memberMapper);
  • 스프링을 띄우지 않으므로 컴포넌트 스캐닝은 일어나지 않습니다. MemberService객체를 직접 생성해줍니다.
  • 생성자주입에 사용했던 Repository나 Mapper 객체를 직접 주입 해줍니다.
  • @Mock을 사용하여 생성했던 memberMapper를 서비스를 생성할때 직접 주입해줍니다.

 

MockitoAnnotations.initMocks(this);

  • @Mock이 붙은 객체를 생성, 초기화해달라는 명령입니다.

 

assert, verify, given은 컨트롤러 테스트코드 작성 게시글에서 설명했으므로 생략하겠습니다.

 

 

5. 테스트코드(MockBean과 SpringBootTest를 사용)

package com.example.junittut.service;

import com.example.junittut.model.Member;
import com.example.junittut.repository.MemberMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;

@SpringBootTest
class MemberServiceTest {

    @Autowired
    MemberService memberService;

    @MockBean
    MemberMapper memberMapper;

    @Test
    @DisplayName("멤버 상세조회")
    void detail() {
        int memberId = 1;
        String memberName = "Tom";

        Member mockMember = Member.builder().id(memberId).name(memberName).build();

        given(memberMapper.selectById(memberId)).willReturn(mockMember);

        Member responseMember = memberService.detail(memberId);

        assertThat(responseMember.getName()).isEqualTo(memberName);
    }

    @Test
    @Transactional
    @DisplayName("멤버 생성")
    void insert() {
        int memberId = 1;
        String memberName = "Tom";

        Member mockMember = Member.builder().id(memberId).name(memberName).build();

        memberService.insert(mockMember);

        verify(memberMapper).insertMember(mockMember);

    }


}

@SpringBootTest

  • 통합테스트 방식으로 스프링을 실제 서버를 띄운것과 동일한 환경을 만들어줍니다.
  • 스프링 컨테이너가 돌아가므로 서비스객체를 Autowired 받을 수 있습니다.

 

@MockBean

  • @SprinbBootTest 어노테이션에 의해 bean들이 모두 로드되지만 기존의 Bean은 MockBean으로 치환됩니다
  • 스프링은 기존에 있던 bean을 가짜 객체로 치환하여 보관하며.
  • 추후 컴포넌트 스캐닝시 스켈레톤 타입의 가짜객체로 DI를 하여 테스트를 돌려도 DB에 접근하지 않게 됩니다.

 

6. 마치며

가짜객체를 만들어 서비스단을 테스트할때는 어쩔수없이 스프링 컨테이너를 띄워야 하는 상황이 아니라면,

MockBean이 아닌 Mock을 사용하여 직접 의존성을 주입해주는 것이 빠릅니다.

 

 

 

반응형

댓글