1. Mock

  • 일반적으로 사용하는 목업(Mock-up)의 의미와 비슷하며, 진짜인 것 처럼 보이도록 만드는 유사한 상황이나 물건, 물질 등을 의미한다
  • 테스트 계통에서의 Mock는 가짜 객체를 의미한다
  • 단위 테스트나 슬라이스 테스트 등에 Mock 객체를 사용하는 것을 Mocking 이라고 한다

2. Mock 객체를 사용하는 이유

  • 테스트의 단위는 적을수록 효율적이다
  • Mock 객체를 사용하지 않으면 Request는 서비스계층을 거쳐 데이터 액세스 계을과 데이터베이스까지 순회한 후 되돌아 온다
  • Mock 객체를 사용하면 Request가 원하는 응답을 상관없는 계층을 제외하고 결과값을 전송할 수 있다

3. Mockito

  • 여러 오픈 소스 라이브러리를 통해 Mock 객체를 생성하고 진짜처럼 동작하게 한다
  • Spring Framework 자체적으로도 지원하고 있는 Mocking 라이브러리가 Mockito이다
  • Mocking 기능을 이용하면 테스트하고자 하는 대상에서 다른 영역(다른 계층 또는 외부 통신이 필요한 서비스 등)을 단절시켜 테스트 대상에만 집중할 수 있다

4. Mockito 예제

 1) MemberController의 postMember() 테스트에 Mockito 적용

package com.codestates.slice.mock;

import com.codestates.member.dto.MemberDto;
import com.codestates.member.entity.Member;
import com.codestates.member.mapper.MemberMapper;
import com.codestates.member.service.MemberService;
import com.codestates.stamp.Stamp;
import com.google.gson.Gson;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;

import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
class MemberControllerMockTest {
    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private Gson gson;

    //@MockBean 애너테이션은 Application Context에 등록되어 있는 Bean에 대한 Mockito Mock 객체를 생성하고
    //주입해주는 역할을 한다
    //@MockBean 애너테이션을 필드에 추가하면 해당 필드의 Bean에 대한 Mock 객체를 생성한 후, 필드에 주입(DI)한다
    @MockBean
    //MemberService 빈에 대한 Mock 객체를 생성해서 memberService 필드에 주입한다
    private MemberService memberService;

    //MemberMapper를 DI 받는 이유는 MockMemberService(가칭)의 createMember()에서 리턴하는 Member 객체를
    //생성하기 위함이다
    @Autowired
    private MemberMapper mapper;

    @Test
    void postMemberTest() throws Exception {
        // given
        MemberDto.Post post = new MemberDto.Post("hgd@gmail.com",
                "홍길동", "010-1234-5678");

        //MemberMapper를 이용해 post(MemberDto.Post 타입) 변수를 Member 객체로 변환하고 있다
        //MemberMapper를 사용하지 않고 new Member() 와 같이 Member 객체를 생성해도 되지만
        //여기서는 post 변수를 재사용하기 위해 MemberMapper로 변환을 했다
        Member member = mapper.memberPostToMember(post);

        //실제 MemberService의 createMember()에서 회원 정보 등록 시, Stamp 정보도 등록되며,
        //createMember() 의 리턴 값(Member 객체)에 Stamp 정보가 포함된다
        //MockMemberService(가칭)의 createMember()에서도 리턴 값으로 Stamp 정보가 포함된 Member 객체를
        //리턴하도록 (4)와 같이 Stamp 객체를 추가해  준다
        member.setStamp(new Stamp());
        //Stamp 객체를 추가해주지 않으면 MemberResponseDto 클래스 객체가 JSON으로 변환되는 과정 중에
        //Stamp에 대한 정보가 없다는 예외가 발생한다


        //Mockito에서 지원하는 Stubbing 메서드이다
        given(memberService.createMember(Mockito.any(Member.class)))
        /*given()은 Mock 객체가 특정 값을 리턴하는 동작을 지정하는데 사용한다
          Mockito에서 지원하는 when()과 동일한 기능을 한다
          Mock 객체인 memberService 객체로 createMember() 메서드를 호출하도록 정의 하였다*/
        /*createMember()의 파라미터인 Mockito.any(Member.class) 는 Mockito에서 지원하는
          변수 타입 중 하나이다
          MockMemberService(가칭)가 아닌 실제 MemberService 클래스에서 createMember()의
          파라미터 타입이 Member이므로 Mockito.any()에 Member.class를 타입으로 지정해 준다*/

        //.willReturn(member)은 MockMemberService(가칭)의 createMember() 메서드가 리턴할
        //Stub 데이터이다
               .willReturn(member);

        String content = gson.toJson(post);

        // when
        ResultActions actions =
                mockMvc.perform(
                        post("/v11/members")
                                .accept(MediaType.APPLICATION_JSON)
                                .contentType(MediaType.APPLICATION_JSON)
                                .content(content)
                );

        // then
        MvcResult result = actions
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.data.email").value(post.getEmail()))
                .andExpect(jsonPath("$.data.name").value(post.getName()))
                .andExpect(jsonPath("$.data.phone").value(post.getPhone()))
                .andReturn();

//        System.out.println(result.getResponse().getContentAsString());
    }
}
  • 실행하면 아래와 같은 결과가 출력된다

  • 실행 결과에서 MockMemberService(가칭)의 createMember() 메서드가 호출되므로, 데이터 액세스 계층쪽의 로직은 실행이 되지 않는다
  • MockMemberService(가칭) 클래스는 테스트하고자 하는 Controller의 테스트에 집중할 수 있도록 다른 계층과의 연동을 끊어주는 역할을 한다
  • MemberService의 클래스 쪽의 createMember()가 호출되지 않고, Mockito가 생성한 MockMemberService(가칭)의 createMember()가 호출되는지 확인해 본다
    - MemberService의 createMember() 메서드 내에 디버깅 용 breakpoint를 추가해서 MemberControllerMockTest 클래스의 실행이 breakpoint에서 멈추는지 확인해 본다
    -  breakpoint 확인을 위한 실행은 Debug 실행으로 해야한다
    - 실행이 멈추지 않고 정상적으로 실행이 된다면 MockMemberService(가칭)쪽의 로직이 실행된다고 유추해 볼 수 있다
  • MemberController의 postMember() 핸들러 메서드 내에서  아래의 코드에 breakpoint를 추가해 본다
 @PostMapping
    public ResponseEntity postMember(@Valid @RequestBody MemberDto.Post requestBody) {
        Member member = mapper.memberPostToMember(requestBody);
        member.setStamp(new Stamp()); // homework solution 추가

  여기에 breakpoint ->     Member createdMember = memberService.createMember(member);

        return new ResponseEntity<>(
                new SingleResponseDto<>(mapper.memberToMemberResponse(createdMember)),
                HttpStatus.CREATED);
    }
  • 디버그 모드에서 MemberControllerMockTest 클래스를 실행해 본다

  • 위와 같이 breakpoint에서 실행이 일시 중지된다
    - IntelliJ IDE의 Debug 창을 확인해 보면 memberService 객체가 Mockito의 Mock 객체인 것을 확인할 수 있다
    - Mockito를 이용하면 의존하는 다른 메서드 호출이나 외부 서비스의 호출을 단절 시켜 원하는 테스트의 범위를 최대한 좁힐 수 있다

▶ IntelliJ debug : https://www.jetbrains.com/help/idea/debug-tool-window.html

 

Debug tool window | IntelliJ IDEA

 

www.jetbrains.com

 

 2) MemberService에 Mockito 적용

  • Mockito를 통해서 DB에서 회원 정보를 조회하지 않고 Member 객체를 제공할 수 있다는 것을 확인해 본다
package com.codestates.slice.mock;

import com.codestates.exception.BusinessLogicException;
import com.codestates.member.entity.Member;
import com.codestates.member.repository.MemberRepository;
import com.codestates.member.service.MemberService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.BDDMockito.given;

//Spring을 사용하지 않고, Junit에서 Mockito의 기능을 사용하기 위해서는 
  @ExtendWith(MockitoExtension.class)를 추가해야 한다
@ExtendWith(MockitoExtension.class)
public class MemberServiceMockTest {

    //@Mock 애너테이션을 추가하면 해당 필드의 객체를 Mock 객체로 생성한다
    @Mock
    private MemberRepository memberRepository;

    //@InjectMocks 애너테이션을 추가한 필드에 MemberRepository Mock 객체를 주입해 준다
    //memberService 객체는 주입 받은 memberRepository Mock 객체를 포함하고 있다
    @InjectMocks
    private MemberService memberService;

    @Test
    public void createMemberTest() {
        // given
        Member member = new Member("hgd@gmail.com", "홍길동", "010-1111-1111");

        //memberRepository Mock 객체로 Stubbing을 하고 있다
        //memberRepository.findByEmail(member.getEmail())의 리턴 값으로 Optional.of(member)를
          지정했기 때문에 테스트 케이스를 실행하면 결과는 “passed”이다
        //Optional.of(member) 의 member 객체에 포함된 이메일 주소가
          memberRepository.findByEmail(member.getEmail()) 에서 파라미터로 전달한 이메일 주소와
          동일하기 때문에 검증 결과가 “passed”이다
        given(memberRepository.findByEmail(member.getEmail()))
                .willReturn(Optional.of(member)); // (5)

        // when / then (6)
        assertThrows(BusinessLogicException.class, () -> memberService.createMember(member));
    }
}

 

 

 

※ 참조 링크

▶ mockito : https://site.mockito.org/

 

Mockito framework site

Intro Why How More Who Links Training Why drink it? Mockito is a mocking framework that tastes really good. It lets you write beautiful tests with a clean & simple API. Mockito doesn’t give you hangover because the tests are very readable and they produc

site.mockito.org

 https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html

 

Mockito - mockito-core 4.6.1 javadoc

Latest version of org.mockito:mockito-core https://javadoc.io/doc/org.mockito/mockito-core Current version 4.6.1 https://javadoc.io/doc/org.mockito/mockito-core/4.6.1 package-list path (used for javadoc generation -link option) https://javadoc.io/doc/org.m

javadoc.io

 

+ Recent posts