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
'Spring MVC' 카테고리의 다른 글
Spring MVC(2) - API 계층 - Controller (MVC 개요/핸들러 메서드) (0) | 2022.09.21 |
---|---|
Spring MVC(1) - 개요 (0) | 2022.09.21 |
Spring MVC - 데이터 액서스 계층(Data Access Layer) - DDD (0) | 2022.07.03 |
Spring MVC - 데이터 액서스 계층(Data Access Layer) - Spring Data JDBC(샘플 구현) (0) | 2022.07.03 |
Spring MVC - 데이터 액서스 계층(Data Access Layer) - Spring Data JDBC(개요) (0) | 2022.07.03 |