Chapter - Mockito
이 전 유닛에서는 Java에서 가장 많이 사용하는 테스트 프레임워크인 JUnit과 Spring Framework에서 지원하는 테스팅 기능을 이용해서 단위 테스트와 슬라이스 테스트를 위한 테스트 케이스를 작성해 보았습니다.
그런데, 테스트 세계에는 우리가 학습한 단위 테스트와 슬라이스 테스트를 조금 더 효과적으로 진행할 수 있는 방법이 존재하는데 그것은 바로 Mock 객체를 테스트에 적용하는 것입니다.
이번 유닛에서는 우리가 작성해 본 단위 테스트와 슬라이스 테스트에 Mock 객체를 어떻게 적용할 수 있는지 자세히 알아보도록 하겠습니다.
추가적으로 TDD(Test Driven Development)가 무엇인지 살펴보고, TDD 개발에 대한 전반적인 흐름을 이해해 보도록 하겠습니다.
이번 유닛의 템플릿 프로젝트와 레퍼런스 코드는 지난 시간까지 학습에 사용했던 템플릿과 레퍼런스 프로젝트를 동일하게 사용합니다.
이 점을 염두에 두고, 학습을 진행해 주세요!
- 테스팅 학습을 위한 템플릿 프로젝트 github 주소
- 테스팅 학습을 위한 학습 참고용 레퍼런스 코드
학습 목표
- Mock의 의미를 이해할 수 있다.
- Mockito의 기본 사용법을 이해할 수 있다.
- 비즈니스 로직의 단위 테스트에 Mockito를 적용할 수 있다.
- Controller의 슬라이스 테스트에 Mockito를 적용할 수 있다.
- TDD(Test Driver Development)의 개념을 이해할 수 있다.
[기본] Mockito란?
Mock이란?
Mock이란 무엇일까요? 우리가 굳이 개발자로서 생활을 하는 것이 아니더라도 회사를 다니다 보면 타 부서의 직원들이 나누는 대화 중에 한 번씩 나올 법한 용어가 바로 목업(Mock-up)입니다.
목업(Mock-up)은 실제 제품이 나오기 전에 내부적으로 사용하기 위한 모형(또는 가짜라고도 합니다) 제품을 의미합니다.
진짜 제품은 아니지만 실제 제품과 유사한 디자인을 가질 수도 있고, 실제 제품처럼 전체 기능이 동작하지는 않지만 일부 기능을 테스트해볼 수 있는 모형 제품인 것입니다.
영어 사전에서 찾아본 mock의 의미
’not real, but intended to be very similar to a real situation, substance etc’
영어 사전에서 확인해 본 mock의 의미도 앞에서 설명한 목업(Mock-up)의 의미와 별반 다르지 않습니다.
진짜인 것처럼 보이도록 진짜와 유사한 상황이나 물건, 물질 등을 의미합니다.
가끔씩은 Mock Human이 있어서 출근하기 싫은 날에는 대신 출근을 해주었으면 하는 생각도 드는군요. ^^
✔ 테스트 세계에서의 Mock
자, 그럼 테스트 세계에서 사용하는 Mock은 무엇을 의미할까요?
테스트 세계에서의 Mock은 바로 가짜 객체를 의미합니다.
그리고 단위 테스트나 슬라이스 테스트 등에 Mock 객체를 사용하는 것을 바로 Mocking이라고 합니다.
테스트에서 Mock 객체를 사용하는 이유
그렇다면 Mock 객체를 사용하지 않고 그냥 테스트를 수행하면 될 텐데 왜 굳이 진짜도 아닌 가짜인 Mock 객체를 사용하는 걸까요?
✅ Mock 객체를 사용하지 않은 슬라이스 테스트 프로세스
[그림 3-75] MemberControllerTest 클래스의 postMemberTest() 테스트 실행 흐름
그림 3-75는 이 전 유닛에서 우리가 작성한 MemberControllerTest의 postMemberTest() 실행 시, 테스트 실행 흐름을 그림으로 표현한 것입니다.
우리가 작성한 MemberControllerTest 클래스는 슬라이스 테스트를 위해 작성한 테스트이지만 완전한 슬라이스 테스트라고 보기 힘듭니다.
그림 3-75를 보면 MemberController의 postMember() 핸들러 메서드 쪽만 테스트를 해야 되는데 서비스 계층을 거쳐서 데이터 액세스 계층 그리고 데이터베이스까지 그 동작 흐름이 끝까지 이어졌다가 되돌아오기때문에 슬라이스 테스트라기보다는 통합 테스트에 가깝습니다.
슬라이스 테스트의 목적은 무엇일까요?
해당 계층 영역에 대한 테스트에 집중하는 것입니다.
그림 3-75에서는 API 계층인 MemberController만 테스트하면 되는데 불필요한 전 계층을 다 거쳐야 되기 때문에 성능 면에서나 테스트 관심 영역 면에서나 슬라이스 테스트의 주목적에 맞지 않습니다.
테스트의 단위는 가급적 작을수록 좋다. 여러분들 이 문장 기억나시죠?
그런데 Mock 객체를 사용하면 우리가 작성한 MemberController에 진정한 슬라이스 테스트를 적용할 수 있습니다.
✅ Mock 객체를 사용한 슬라이스 테스트 프로세스
[그림 3-76] Mock 객체를 사용한 MemberControllerTest 클래스의 postMemberTest() 테스트 실행 흐름
그림 3-76은 Mock 객체를 사용한 MemberControllerTest 클래스의 테스트 실행 흐름입니다.
Mock 객체를 사용하지 않을 때와 Mock 객체를 사용할 때의 차이점이 한눈에 들어올 거라 생각합니다.
그림 3-76에서는 MemberController에서 MemberService 클래스의 createMember()를 호출하지 않고, MockMemberService 클래스의 createMember()를 호출하고 있습니다.
MemberService 클래스에 접근을 했다면 데이터베이스에서 데이터를 조회하는 과정을 거쳤을 텐데, Mock 객체를 이용함으로써 다른 계층과 단절되어 불필요한 과정을 줄일 수 있습니다.
그리고 이제 우리가 테스트하려는 대상에 집중해서 테스트를 수행할 수 있습니다.
Mockito란?
그런데 이렇게 Mock 객체를 생성하고, 해당 Mock 객체가 진짜처럼 동작하게 하는 역할을 구체적으로 누가 담당할까요?
Mock 객체로 Mocking을 할 수 있게 해주는 여러 오픈 소스 라이브러리가 있지만 그중에서 가장 많이 사용되고, Spring Framework 자체적으로도 지원하고 있는 Mocking 라이브러리는 바로 Mockito입니다.
우리는 Mockito의 Mocking 기능을 이용해서 테스트하고자 하는 대상에서 다른 영역(다른 계층 또는 외부 통신이 필요한 서비스 등)을 단절시켜 오로지 테스트 대상에만 집중할 수 있습니다.
슬라이스 테스트에 Mockito 적용
✔ MemberController의 postMember() 테스트에 Mockito 적용
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("hgd@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/"))));
}
}
[3-197] Mockito 적용한 MemberControllerMockTest 클래스
코드 3-197에서는 기존에 우리가 Mockito 없이 MemberController의 postMember() 핸들러 메서드를 테스트했던 테스트 케이스에 Mockito를 적용했습니다.
- @MockBean 애너테이션은 Application Context에 등록되어 있는 Bean에 대한 Mockito Mock 객체를 생성하고 주입해 주는 역할을 합니다.(1)에서는 MemberService 빈에 대한 Mock 객체를 생성해서 memberService 필드에 주입합니다.
- (1)과 같이 @MockBean 애너테이션을 필드에 추가하면 해당 필드의 Bean에 대한 Mock 객체를 생성한 후, 필드에 주입(DI)합니다.
- (2)에서 MemberMapper를 DI 받는 이유는 MockMemberService(가칭)의 createMember()에서 리턴하는 Member 객체를 생성하기 위함입니다.
- (3)에서 MemberMapper를 이용해 post(MemberDto.Post 타입) 변수를 Member 객체로 변환하고 있습니다.
- MemberMapper를 굳이 사용하지 않고 new Member()와 같이 Member 객체를 생성해도 되지만 여기서는 post 변수를 재사용하기 위해 MemberMapper로 변환을 했습니다.
- 실제 createMember()의 리턴 값(Member 객체)에는 memberId가 포함이 되는데 이 memberId는 response의 Location header에 포함이 되어야 하므로 (4)와 같이 **MockMemberService(가칭)**의 createMember()에서도 memberId를 리턴해 줄 수 있도록 memberId를 추가해 줍니다.
- (5)는 Mockito에서 지원하는 Stubbing 메서드입니다.
- given(memberService.createMember(Mockito.any(Member.class)))given()은 Mock 객체가 특정 값을 리턴하는 동작을 지정하는 데 사용하며, Mockito에서 지원하는 when()과 동일한 기능을 합니다.createMember()의 파라미터인 Mockito.any(Member.class)는 Mockito에서 지원하는 변수 타입 중 하나입니다.따라서 Mockito.any()에 Member.class를 타입으로 지정해 주었습니다.
- MockMemberService(가칭)가 아닌 실제 MemberService 클래스에서 createMember()의 파라미터 타입은 무엇인가요? 바로 Member 타입입니다.
- 여기서는 Mock 객체인 memberService 객체로 createMember() 메서드를 호출하도록 정의하고 있습니다.
- .willReturn(member)
- MockMemberService(가칭)의 createMember() 메서드가 리턴할 Stub 데이터입니다.
Stubbing이란?
Stubbing은 테스트를 위해서 Mock 객체가 항상 일정한 동작을 하도록 지정하는 것을 의미합니다.
코드 3-197의 (5)에서는 MockMemberService(가칭)의 createMember() 메서드가 항상 동일한 member 객체를 리턴하고 있습니다.
이제 MemberControllerMockTest 클래스의 postMemberTest()를 실행해 보겠습니다.
...
...
2022-07-08 20:29:29.258 INFO 19036 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2022-07-08 20:29:29.261 INFO 19036 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2022-07-08 20:29:29.266 INFO 19036 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
BUILD SUCCESSFUL in 15s
// 실제 MemberService의 createMember()가 호출되면 Member, Stamp 등록 등의 insert query 문이 출력되지만
// MockMemberService(가칭)의 createMember()가 호출되므로 Member 등록에 대한 쿼리가 출력되지 않음.
실행 결과를 보면, MockMemberService(가칭)의 createMember() 메서드가 호출되므로, 데이터 액세스 계층 쪽의 로직은 실행이 되지 않습니다.
즉, MockMemberService(가칭) 클래스는 우리가 테스트하고자 하는 Controller의 테스트에 집중할 수 있도록 다른 계층과의 연동을 끊어주는 역할을 하는 것입니다.
MemberService의 클래스 쪽의 createMember()가 호출되지 않고, Mockito가 생성한 MockMemberService(가칭)의 createMember()가 호출되는지 확인하고 싶다면 아래와 같이 해보세요.
- MemberService의 createMember() 메서드 내에 디버깅 용 breakpoint를 추가해서 MemberControllerMockTest 클래스의 실행이 breakpoint에서 멈추는지 확인해 보세요.
멈추지 않고, 테스트 케이스가 정상적으로 실행이 된다면 MockMemberService(가칭)쪽의 로직이 실행된다고 유추해 볼 수 있습니다.
- MemberController의 postMember() 핸들러 메서드 내에서 아래의 그림 3-77과 같이 breakpoint를 추가해 보세요.
[그림 3-77] MemberController의 postMember()에 breakpoint를 추가한 모습
그림 3-77과 같이 breakpoint를 추가한 후에 MemberControllerMockTest 클래스를 디버그 모드로 다시 실행해 봅니다. (벌레 모양의 버튼을 눌러야 됩니다. ^^)
[그림 3-78] 디버깅 모드에서 memberService 객체
MemberControllerMockTest 클래스를 디버그 모드로 실행하면 그림 3-77과 같이 breakpoint에서 실행이 일시 중지됩니다.
이때, IntelliJ IDE의 Debug 창을 확인해 보면 그림 3-78과 같이 memberService 객체의 정체를 확인할 수 있습니다.
보다시피 Mockito의 Mock 객체입니다.
이처럼 Mockito를 잘 이용하면 의존하는 다른 메서드 호출이나 외부 서비스의 호출을 단절시킬 수 있기 때문에 우리가 원하는 테스트의 범위를 최대한 좁힐 수 있습니다.
✔ MemberService의 createMember() 테스트에 Mockito 적용
그럼 이번에는 MemberService 클래스의 createMember() 메서드를 테스트해 봅시다.
MemberService 클래스의 createMember() 메서드는 비즈니스 로직입니다.
일반적으로 비즈니스 로직은 데이터 액세스 계층과는 무관하게 서비스 계층에 구현된 비즈니스 로직 자체를 Spring Framework의 도움을 받지 않고도 빠르게 테스트를 진행할 수 있어야 합니다.
@Transactional
@Service
public class MemberService {
private final MemberRepository memberRepository;
private final ApplicationEventPublisher publisher;
public MemberService(MemberRepository memberRepository,
ApplicationEventPublisher publisher) {
this.memberRepository = memberRepository;
this.publisher = publisher;
}
public Member createMember(Member member) {
verifyExistsEmail(member.getEmail()); // (1)
Member savedMember = memberRepository.save(member);
publisher.publishEvent(new MemberRegistrationApplicationEvent(this, savedMember));
return savedMember;
}
...
...
private void verifyExistsEmail(String email) {
Optional<Member> member = memberRepository.findByEmail(email); // (2)
// (3)
if (member.isPresent())
throw new BusinessLogicException(ExceptionCode.MEMBER_EXISTS);
}
}
[3-198] 테스트를 진행할 MemberService 클래스 코드 일부
코드 3-198은 테스트를 진행할 MemberService 클래스 코드의 일부입니다.
우리가 테스트하고자 하는 부분은 createMember() 메서드의 (1)과 같이 DB에 존재하는 이메일인지 여부를 검증하는 verifyExistsEmail() 메서드가 정상적인 동작을 수행하는지를 테스트하는 것입니다.
그런데 verifyExistsEmail() 메서드의 내부를 보면 (2)와 같이 verifyExistsEmail() 메서드의 파라미터로 전달받은 email을 조건으로 한 회원 정보가 있는지 memberRepository.findByEmail(email)을 통해 DB에서 조회하고 있습니다.
하지만 우리는 verifyExistsEmail() 메서드가 DB에서 Member 객체를 잘 조회하는지 여부를 테스트하려는게 아니라, 어디서 조회해 왔든 상관없이 조회된 Member 객체가 null이 아니라면 BusinessLogicException을 잘 던지는지 여부만 테스트하면 됩니다.
(3)과 같이 단 두 줄짜리 코드이더라도 member 객체 null 여부를 판단하는 것 역시 엄연히 비즈니스 로직이라고 볼 수 있습니다.
따라서 DB에서 회원 정보를 조회하는 (2)의 memberRepository.findByEmail(email) 은 Mocking의 대상이 됩니다.
다시 한번 말하지만 비즈니스 로직 자체를 테스트하는 것이기 때문에 Member 객체를 어디에서 얻어오든지 상관없습니다.
즉, DB에서 회원 정보를 조회하지 않고, Mocking을 통해서 Member 객체를 제공할 수 있다는 것입니다.
누구를 통해서? Mockito를 통해서.
그럼 이제 Mockito를 이용해서 Mocking을 해 봅시다.
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("hgd@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));
}
}
[3-199] MemberService의 createMember()를 테스트하는 MemberServiceMockTest 클래스
코드 3-199는 MemberService의 createMember()를 테스트하는 MemberServiceMockTest 클래스입니다.
- Spring을 사용하지 않고, Junit에서 Mockito의 기능을 사용하기 위해서는 (1)과 같이 @ExtendWith(MockitoExtension.class)를 추가해야 합니다.
- (2)와 같이 @Mock 애너테이션을 추가하면 해당 필드의 객체를 Mock 객체로 생성합니다.
- 그리고 (3)과 같이 @InjectMocks 애너테이션을 추가한 필드에 (2)에서 생성한 Mock 객체를 주입해 줍니다. 즉, (3)의 memberService 객체는 주입받은 memberRepository Mock 객체를 포함하고 있습니다.
- (4)에서는 (2)에서 생성한 memberRepository Mock 객체로 Stubbing을 하고 있습니다.
- memberRepository.findByEmail(Mockito.anyString())의 리턴 값으로 (5)와 같이 Optional.of(member)를 지정했기 때문에 테스트 케이스를 실행하면 결과는 “passed”입니다.
Optional.of(member)의 member 객체에 포함된 이메일 주소가 memberService.createMember(member)에서 파라미터로 전달한 member 객체에 포함된 이메일 주소와 동일하기 때문에 검증 결과가 “passed”라는 사실을 잘 이해하길 바랍니다.
핵심 포인트
- 테스트 세계에서의 Mock은 바로 가짜 객체를 의미한다.
- Mockito는 Mock 객체를 생성하고, 해당 Mock 객체가 진짜처럼 동작하게 하는 기능을 하는 Mocking framework(또는 라이브러리)이다.
- @MockBean 애너테이션은 Application Context에 등록되어 있는 Bean에 대한 Mockito Mock 객체를 생성하고 주입해 주는 역할을 한다.
- Junit에서 Spring을 사용하지 않고 순수하게 Mockito의 기능만을 사용하기 위해서는 @ExtendWith(MockitoExtension.class)를 클래스 레벨에 추가해야 한다.
- @Mock 애너테이션을 추가하면 해당 필드의 객체를 Mock 객체로 생성한다.
- @Mcok 애너테이션을 통해 생성된 Mock 객체는 @InjectMocks 애너테이션을 추가한 필드에 주입된다.
심화 학습
- Mockito의 사용법을 더 알아보고 싶다면 아래 링크를 클릭하세요.
[실습] Mockito 실습
Mockito 실습 개요
이번 실습은 슬라이스 테스트와 비즈니스 로직 테스트에 Mockito를 적용해 보는 실습입니다.
- 지난 챕터까지 학습했던 구현 코드들이 기본적으로 포함이 되어 있으며, 이를 기반으로 요구 사항에 맞게 Mockito를 사용하여 테스트 케이스를 작성하면 됩니다.
- 실습용 프로젝트 패키지는 ‘com.springboot’ 패키지 하위에 아래와 같이 구성되어 있습니다.
- advice
- audit
- coffee
- config
- dto
- exception
- member
- order
- response
- stamp
- validator
- 단위 테스트를 위한 테스트 케이스는 일반적으로 Gradle 기반 프로젝트에서 ‘src/test/java/**’ 경로에 작성합니다.
- 여러분들이 작성할 테스트 케이스 역시 ‘src/test/java/com/springboot/homework’의 실습 과제용 테스트 클래스(MemberControllerHomeworkTest , MemberServiceHomeworkTest) 내부에 포함되어 있습니다.
실습 사전 준비
- 실습용 샘플 프로젝트 복제
- 아래 github 링크에서 실습용 repository를 fork합니다.
- fork한 repository를 여러분의 PC에서 git clone 명령으로 local repository에 복제합니다.
- IntelliJ IDE로 clone 받은 forked local repository 디렉토리의 프로젝트를 Open합니다.
- 아래 실습 요구 사항에 따라 실습을 진행합니다.
- 작성한 코드는 main branch에 작성해 주세요.
- main branch가 아닌 별도의 branch를 생성해서 작업을 했다면 작업이 끝난 후, 반드시 main branch로 merge 해야 합니다.
실습 과제 내용
실습 1: MemberController 클래스에 대한 슬라이스(Slice) 테스트 케이스 작성(Mockito 사용)
- 테스트 케이스 작성을 위한 설명
- MemberController의 각 핸들러 메서드가 클라이언트의 요청을 잘 전달받고, 응답을 잘 전달하는지 테스트하세요.
- 현재 여러분들이 완성해야 할 MemberControllerHomeworkTest 테스트 클래스가 아래 경로에 비어 있는 채로 작성되어 있습니다.
- src/test/java/com/springboot/homework/MemberControllerHomeworkTest.java
- MemberControllerHomeworkTest 테스트 클래스에서 비어 있는 아래 테스트 케이스를 작성해 주세요.
- patchMemberTest()
- MemberController 클래스의 patchMember() 핸들러 메서드를 테스트하기 위한 테스트 케이스입니다.
- getMemberTest()
- MemberController 클래스의 getMember() 핸들러 메서드를 테스트하기 위한 테스트 케이스입니다.
- getMembersTest()
- MemberController 클래스의 getMembers() 핸들러 메서드를 테스트하기 위한 테스트 케이스입니다.
- deleteMemberTest()
- MemberController 클래스의 deleteMember() 핸들러 메서드를 테스트하기 위한 테스트 케이스입니다.
- patchMemberTest()
- 제한 사항
- ⭐ Mockito를 사용한 Mocking을 통해 비즈니스 로직과 데이터 액세스 계층을 거쳐 DB에서 데이터를 가져오는 로직을 단절시켜야 합니다.
- 즉, Controller의 로직에 대한 테스트에 집중할 수 있도록 테스트 케이스를 작성해야 합니다.
- 이외에 테스트에 대한 별도의 제한 사항은 없습니다. 여러분들이 해당 Controller 클래스의 핸들러 메서드를 원하는 방법대로 테스트할 수 있도록 테스트 케이스를 작성하면 됩니다.
- 테스트 입력 값에 별도의 제한 사항은 없습니다.
- 여러분이 다양한 상황을 테스트해보세요.
- ⭐ Mockito를 사용한 Mocking을 통해 비즈니스 로직과 데이터 액세스 계층을 거쳐 DB에서 데이터를 가져오는 로직을 단절시켜야 합니다.
실습 2: OrderService 클래스에 대한 비즈니스 로직 테스트 케이스 작성(Mockito 사용)
- 테스트 케이스 작성을 위한 설명
- OrderService 클래스의 cancelOrder() 메서드의 비즈니스 로직이 잘 동작하는지 테스트하세요.
public void cancelOrder(long orderId) { Order findOrder = findVerifiedOrder(orderId); int step = findOrder.getOrderStatus().getStepNumber(); // OrderStatus의 step이 2 이상일 경우(ORDER_CONFIRM)에는 주문 취소가 되지 않도록한다. if (step >= 2) { throw new BusinessLogicException(ExceptionCode.CANNOT_CHANGE_ORDER); } findOrder.setOrderStatus(Order.OrderStatus.ORDER_CANCEL); orderRepository.save(findOrder); }
- cancelOrder() 메서드의 로직은 다음과 같습니다.
- calcelOrder() 메서드 내의 로직 중에서 OrderStatus의 step이 2 이상일 경우 BusinessLogicException을 발생(throw)시키는지를 테스트하면 됩니다.
- 현재 여러분들이 완성해야 할 OrderServiceHomeworkTest 테스트 클래스가 아래 경로에 비어 있는 채로 작성되어 있습니다.
- src/test/java/com/springboot/homework/OrderServiceHomeworkTest.java
- OrderServiceHomeworkTest 테스트 클래스에서 비어 있는 아래 테스트 케이스를 작성해 주세요.
- cancelOrderTest()
- OrderSevice 클래스의 cancelOrder() 메서드를 테스트하기 위한 테스트 케이스입니다.
- cancelOrderTest()
- 제한 사항
- ⭐ Mockito를 사용한 Mocking을 통해 데이터 액세스 계층을 거쳐 DB에서 데이터를 가져오는 로직을 단절시켜야 합니다.
- 즉, 서비스 클래스 로직에 대한 테스트에 집중할 수 있도록 테스트 케이스를 작성해야 합니다.
- 이외에 테스트에 대한 별도의 제한 사항은 없습니다. 여러분들이 해당 Controller 클래스의 핸들러 메서드를 원하는 방법대로 테스트할 수 있도록 테스트 케이스를 작성하면 됩니다.
- 테스트 입력 값에 별도의 제한 사항은 없습니다.
- 여러분이 다양한 상황을 테스트해보세요.
- ⭐ Mockito를 사용한 Mocking을 통해 데이터 액세스 계층을 거쳐 DB에서 데이터를 가져오는 로직을 단절시켜야 합니다.
MemberControllerHomework
package com.springboot.homework;
import com.google.gson.Gson;
import org.junit.jupiter.api.Test;
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.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@SpringBootTest
@AutoConfigureMockMvc
public class MemberControllerHomeworkTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private Gson gson;
@Test
void patchMemberTest() throws Exception {
// TODO MemberController의 patchMember() 핸들러 메서드를 테스트하는 테스트 케이스를 여기에 작성하세요.
// TODO Mockito를 사용해야 합니다. ^^
}
@Test
void getMemberTest() throws Exception {
// TODO MemberController의 getMember() 핸들러 메서드를 테스트하는 테스트 케이스를 여기에 작성하세요.
// TODO Mockito를 사용해야 합니다. ^^
}
@Test
void getMembersTest() throws Exception {
// TODO MemberController의 getMembers() 핸들러 메서드를 테스트하는 테스트 케이스를 여기에 작성하세요.
// TODO Mockito를 사용해야 합니다. ^^
}
@Test
void deleteMemberTest() throws Exception {
// TODO MemberController의 deleteMember() 핸들러 메서드를 테스트하는 테스트 케이스를 여기에 작성하세요.
// TODO Mockito를 사용해야 합니다. ^^
}
}
OrderServcieHomeworkTest
package com.springboot.homework;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class OrderServiceHomeworkTest {
@Test
public void cancelOrderTest() {
// TODO OrderService의 cancelOrder() 메서드를 테스트하는 테스트 케이스를 여기에 작성하세요.
// TODO Mockito를 사용해야 합니다. ^^
}
}