[Spring MVC] Mockito 실습
1. MemberController 클래스에 대한 Slice 테스트 케이스 작성 (Mockito)
Mockito를 사용한 Mocking을 통해 비즈니스 로직과 데이터 액세스 계층을 거쳐 DB에서 데이터를 가져오는 로직을 단절시켜야 한다.
@Transactional
@SpringBootTest
@AutoConfigureMockMvc
public class MemberControllerHomeworkTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private Gson gson;
@MockBean
private MemberService memberService;
// MemberMapper를 DI 받는 이유는 MockMemberService(가칭)의 createMember() 에서 리턴하는 객체를 생성하기 위해.
@Autowired
private MemberMapper mapper;
@Test
void patchMemberTest() throws Exception {
}
@Test
void getMemberTest() throws Exception {
}
@Test
void getMembersTest() throws Exception {
}
@Test
void deleteMemberTest() throws Exception {
}
}
patchMemberTest()
@Test
void patchMemberTest() throws Exception {
MemberDto.Post post = new MemberDto.Post("abc@gmail.com","miniDemuu","010-1234-5678");
// MemberMapper를 이용해 post(MemberDto.Post 타입) 변수를 Member 객체로 변환
// MemberMapper를 굳이 사용하지 않고 new Member() 와 같이 Member 객체를 생성해도 되지만
// post 변수를 재사용하기 위해 MemberMapper로 변환
Member postMember = mapper.memberPostToMember(post);
postMember.setMemberId(1L);
given(memberService.createMember(Mockito.any(Member.class))).willReturn(postMember); // (1)
String postContent = gson.toJson(post);
mockMvc.perform(
post("/v11/members")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content(postContent)
);
MemberDto.Patch patch = new MemberDto.Patch(1L, "demuu", "010-9876-5432", Member.MemberStatus.MEMBER_ACTIVE);
Member patchMember = mapper.memberPatchToMember(patch);
patchMember.setStamp(new Stamp());
given(memberService.updateMember(Mockito.any(Member.class))).willReturn(patchMember);
String patchContent = gson.toJson(patch);
mockMvc.perform(
patch("/v11/members/1")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content(patchContent)
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.name").value(patch.getName()));
}
(1)는 Mockito에서 지원하는 Stubbing 메서드
given()은 Mock 객체가 특정 값을 리턴하는 동작을 지정하는데 사용, Mockito에서 지원하는 when()과 동일한 기능이다.
여기서는 Mock 객체인 memberService 객체로 createMember() 메서드를 호출하도록 정의되있다.
createMember()의 파라미터인 Mockito.any(Member.class)는 Mockito에서 지원하는 변수 타입 중 하나이다.
실제 MemberService 클래스에서 createMember()의 파라미터 타입은 Member 타입이다.
따라서 Mockito.any()에 Member.class를 타입으로 지정해준다.
.willReturn(member)는 MockMemberService(가칭)의 createMember() 메서드가 리턴 할 stub 데이터이다.
다른 방식
@Test
void patchMemberTest() throws Exception {
long memberId = 1L;
MemberDto.response response = new MemberDto.response(memberId, "hgd@gmail.com", "홍길동", "010-1111-1111", Member.MemberStatus.MEMBER_ACTIVE, new Stamp());
given(memberService.findMember(Mockito.anyLong())).willReturn(new Member());
given(mapper.memberToMemberResponse(Mockito.any(Member.class))).willReturn(response);
ResultActions actions = mockMvc.perform(
get("/v11/members/" + response.getMemberId())
.accept(MediaType.APPLICATION_JSON)
)
.andExpectAll(
status().isOk(),
jsonPath("$.data.email").value(response.getEmail()),
jsonPath("$.data.name").value(response.getName()),
jsonPath("$.data.phone").value(response.getPhone())
);
}
getMemberTest()
@Test
void getMemberTest() throws Exception {
MemberDto.response response = new MemberDto.response(
1L, "hgd@gmail.com","홍길동", "010-1111-1111", Member.MemberStatus.MEMBER_ACTIVE, new Stamp());
given(memberService.findMember(Mockito.anyLong())).willReturn(new Member());
given(mapper.memberToMemberResponse(Mockito.any(Member.class))).willReturn(response);
ResultActions actions = mockMvc.perform(
get("/v11/members/" + response.getMemberId())
.accept(MediaType.APPLICATION_JSON)
)
.andExpectAll(
status().isOk(),
jsonPath("$.data.email").value(response.getEmail()),
jsonPath("$.data.name").value(response.getName()),
jsonPath("$.data.phone").value(response.getPhone())
);
}
getMembersTest()
@Test
void getMembersTest() throws Exception {
String page = "1";
String size = "5";
Member member1 = new Member();
Member member2 = new Member();
List<MemberDto.response> responses = List.of(
new MemberDto.response(1L, "abc@gmail.com","MiniDemuu", "010-1111-1111", Member.MemberStatus.MEMBER_ACTIVE, new Stamp()),
new MemberDto.response(2L, "def@gmail.com","Demuu", "010-2222-2222", Member.MemberStatus.MEMBER_ACTIVE, new Stamp())
);
Page<Member> members = new PageImpl<>(List.of(member1,member2));
given(memberService.findMembers(Mockito.anyInt(),Mockito.anyInt())).willReturn(members);
given(mapper.membersToMemberResponses(Mockito.any(List.class))).willReturn(responses);
mockMvc.perform(
get("/v11/members")
.param("page", page)
.param("size", size)
.accept(MediaType.APPLICATION_JSON)
)
.andExpectAll(
status().isOk(),
jsonPath("$.data.*", hasSize(2))
);
}
deleteMemberTest()
@Test
void deleteMemberTest() throws Exception {
doNothing().when(memberService).deleteMember(Mockito.anyLong());
mockMvc.perform(
delete("/v11/members/" + 1L)
.accept(MediaType.APPLICATION_JSON)
) .andExpect(status().isNoContent());
}
Mockito의 doNothing() 메서드는 아무 일도 하지 않는 빈 메서드를 생성한다.
Mockito의 .anyLong() 메서드는 어떤 Long 값이든 인자로 사용될 수 있다는 것을 다타낸다.
2. OrderService 클래스에 대한 비즈니스 로직 테스트 케이스 작성 (Mockito)
OrderService 클래스의 cancelOrder() 메서드의 비즈니스 로직이 잘 동작하는지 테스트
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);
}
OrderStatus의 step이 2이상일 경우 BusinessLogicException을 throw 시키는지 테스트
@ExtendWith(MockitoExtension.class)
public class OrderServiceHomeworkTest {
@Mock
private OrderRepository orderRepository;
@InjectMocks
private OrderService orderService;
@Test
public void cancelOrderTest() {
Order order = new Order();
order.setOrderId(1L);
order.setOrderStatus(Order.OrderStatus.ORDER_COMPLETE);
given(orderRepository.findById(Mockito.anyLong())).willReturn(Optional.of(order));
assertThrows(BusinessLogicException.class, () -> orderService.cancelOrder(order.getOrderId()));
}
}
댓글남기기