공부기록/Spring

[Spring] Mockito // 25.01.23

taecode 2025. 1. 23. 14:52

Mock

 

Mock이란?

- Mock-up : 실제 제품이 나오기 전에 내부적으로 사용하기 위한 모형(or 가짜)

 

- mock : 진짜인 것처럼 보이도록 진짜와 유사한 상황이나 물건, 물질 등을 의미.

 

 - 테스트 세계에서 Mock : 가짜 객체
 - Moking : 단테나 슬라이스 테스트 등에 Mock 객체를 사용하는 것.

 

 

테스트에서 Mock 객체를 사용하는 이유

 

 -> Mock 객체를 사용하지 않은 슬라이스 테스트 프로세스

슬라이스 테스트라면 MemberController의 메서드만 테스트를 해야 하는데 서비스 계층을 거쳐서 데이터 액세스 계층, DB까지 동 작 흐름이 끝까지 이어졌다가 돌아와야하는데 기존 테스트는 통합 테스트에 가깝다.

 

슬라이스 테스트의 목적 : 해당 계층 영역에 대한 테스트에 집중하는 것.

 

-> Mock 객체를 사용한 슬라이스 테스트 프로세스

Controller에서 Service클래스를 호출하는 것이 아닌 MockService 클래스의 메서드를 호출하고 있기 때문에 다른 계층과 단절되어 불필요한 과정을 줄일 수 있다.

 

 

 

Mockito란?

 

Mockito

Mock 객체를 생성하고 해당 Mock 객체가 진짜처럼 동작하게 하는 역할을 할 수 있게 하는 여러 라이브러리가 있지만 가장 많이 사용되고, Spring Framework 자체적으로도 지원하고 있는 Mocking 라이브러리이다.
Mockito의 Mocking 기능을 이용해서 테스트하고자 하는 대상에서 다른 계층을 단절시켜 오로지 테스트 대상에만 집중할 수 있다.

 

 

슬라이스 테스트에 Mockito 적용

Controller의 테스트에 적용

package com.springboot.slice.mock;

import com.springboot.member.dto.MemberDto;
import com.springboot.member.entity.Member;
import com.springboot.member.mapper.MemberMapper;
import com.springboot.member.service.MemberService;
import com.springboot.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 org.springframework.transaction.annotation.Transactional;

import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
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;

    // (1)
    @MockBean
    private MemberService memberService;

    // (2)
    @Autowired
    private MemberMapper mapper;

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

        Member member = mapper.memberPostToMember(post);  // (3)
        member.setMemberId(1L);     // (4)

        // (5)
        given(memberService.createMember(Mockito.any(Member.class)))
                .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
       actions
                .andExpect(status().isCreated())
                .andExpect(header().string("Location", is(startsWith("/v11/members/"))));
    }
}
  1. @MockBean 어노테이션은 Application Context에 등록되어 있는 Bean에 대한 Mockito Mock객체를 생성하고 주입해 주는 역할을 한다.
     - 필드에 추가하면 해당 필드의 Bean에 대한 Mock 객체를 생성한 후, 필드에 주입(DI)한다.

  2. Mapper를 DI 받는 이유는 createMember() 에서 리턴하는 Member 객체를 생성하기 위해서임.

  3. Mapper를 사용하지 않고 Member 객체를 생성해도 되는데 post 변수 재사용을 위해 Mapper로 변환함.

  4. 실제 createMember() 의 리턴 값엔ㄴ memberId가 포함이 되는데 response의 Location header에 포함이 되어야 해서 memberId를 리턴해 줄 수 있도록 추가함

  5. Mockito에서 지원하는 Stubbing 메서드
    given()은 Mock 객체가 특정 값을 리턴하는 동작을 지정하는 데 사용함.
- Stubbing이란 ?
테스트를 위해서 Mock 객체가 항상 일정한 동작을 하도록 지정하는 것.

 

 

서비스 계층에 적용

package com.springboot.slice.mock;

import com.springboot.exception.BusinessLogicException;
import com.springboot.member.entity.Member;
import com.springboot.member.repository.MemberRepository;
import com.springboot.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;

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

    @InjectMocks    // (3)
    private MemberService memberService;

    @Test
    public void createMemberTest() {
        // given
        Member member = new Member("taekho@gmail.com", "택", "010-1111-1111");

        // (4)
        given(memberRepository.findByEmail(Mockito.anyString())).willReturn(Optional.of(member)); // (5)

				// when / then (6)
        assertThrows(BusinessLogicException.class, () -> memberService.createMember(member));
    }
}
  1. Spring을 사용하지 않고 JUni에서 Mockito의 기능을 사용하려면 @ExtendWith(MockitoExtension.class) 를 추가해야함.

  2. @Mock 어노테이션을 추가하면 해당 필드의 객체를 Mock객체로 생성

  3. @InjectMocks 어노테이션을 추가한 필드에 @Mock 객체를 포함하고 있음.

  4. memberRepository Mock 객체로 Stubbing 하고 있다.

 

 

-----------------------------------------

@MockBean

은 내부의 리턴값이 null로 다 바뀜

 

Mockito Stubbing 메서드

 

메서드 설명 사용 예제
thenReturn 특정 호출에 대해 지정된 값을 반환하도록 설정합니다. when(mock.method()).thenReturn(value);
when(mock.get(0)).thenReturn("test");
thenThrow 특정 호출에 대해 지정된 예외를 던지도록 설정합니다. when(mock.method()).thenThrow(new RuntimeException());
thenAnswer 호출 시 동적으로 값을 계산하거나 커스텀 로직을 실행할 수 있도록 설정합니다. when(mock.method()).thenAnswer(invocation -> "dynamic value");
doReturn void 메서드 또는 특정 상황에서 반환값을 설정할 때 사용합니다. doReturn(value).when(mock).voidMethod();
doThrow void 메서드에서 예외를 던지도록 설정합니다. doThrow(new RuntimeException()).when(mock).voidMethod();
doAnswer void 메서드 호출 시 동적으로 값을 계산하거나 커스텀 로직을 실행할 수 있도록 설정합니다. doAnswer(invocation -> { /* custom logic */ }).when(mock).voidMethod();
thenCallRealMethod Mock 객체의 실제 메서드를 호출하도록 설정합니다. when(mock.method()).thenCallRealMethod();
thenReturn(...).thenThrow(...) 여러 호출에 대해 순차적으로 다른 값을 반환하거나 예외를 던질 수 있습니다. when(mock.method()).thenReturn(value1).thenThrow(new RuntimeException());