[Testing] Mockito

Updated:

Categories:

Tags: , , ,

๐Ÿ“Œ ๊ฐœ์ธ์ ์ธ ๊ณต๊ฐ„์œผ๋กœ ๊ณต๋ถ€๋ฅผ ๊ธฐ๋กํ•˜๊ณ  ๋ณต์Šตํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๋ธ”๋กœ๊ทธ์ž…๋‹ˆ๋‹ค.
์ •ํ™•ํ•˜์ง€ ์•Š์€ ์ •๋ณด๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ฐธ๊ณ ๋ฐ”๋ž๋‹ˆ๋‹ค :๐Ÿ˜ธ
[ํ‹€๋ฆฐ ๋‚ด์šฉ์€ ๋Œ“๊ธ€๋กœ ๋‚จ๊ฒจ์ฃผ์‹œ๋ฉด ๋ณต๋ฐ›์œผ์‹ค๊ฑฐ์—์š”]

Mockito

โœ… Mock ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์€ ์Šฌ๋ผ์ด์Šค ํ…Œ์ŠคํŠธ ํ”„๋กœ์„ธ์Šค

โœ… Mock ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•œ ์Šฌ๋ผ์ด์Šค ํ…Œ์ŠคํŠธ ํ”„๋กœ์„ธ์Šค

Mockito๋ž€?

  • Spring Framework ์ž์ฒด์ ์œผ๋กœ๋„ ์ง€์›ํ•˜๊ณ  ์žˆ๋Š” Mocking ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ โ†’ Mockito
  • Mock ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ํ•ด๋‹น Mock ๊ฐ์ฒด๊ฐ€ ์ง„์งœ์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜๊ฒŒ ํ•˜๋Š” ์—ญํ• ์„ ๋‹ด๋‹น
  • Mockito์˜ Mocking ๊ธฐ๋Šฅ์„ ์ด์šฉํ•ด์„œ ํ…Œ์ŠคํŠธํ•˜๊ณ ์ž ํ•˜๋Š” ๋Œ€์ƒ์—์„œ
    ๋‹ค๋ฅธ ์˜์—ญ(๋‹ค๋ฅธ ๊ณ„์ธต ๋˜๋Š” ์™ธ๋ถ€ ํ†ต์‹ ์ด ํ•„์š”ํ•œ ์„œ๋น„์Šค ๋“ฑ)์„ ๋‹จ์ ˆ์‹œ์ผœ ์˜ค๋กœ์ง€ ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์—๋งŒ ์ง‘์ค‘ ๊ฐ€๋Šฅ

์Šฌ๋ผ์ด์Šค ํ…Œ์ŠคํŠธ์— Mockito ์ ์šฉ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Test
void postMemberTest() throws Exception {
    // given
    //MemberDto.Post ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ํ…Œ์ŠคํŠธ์— ์‚ฌ์šฉํ•  ๋ฐ์ดํ„ฐ๋ฅผ ์„ค์ •
    MemberDto.Post post = new MemberDto.Post("jerry@gmail.com","์ œ๋ฆฌ","010-1111-1111");
    
    // ์ƒ์„ฑ๋œ MemberDto.Post ๊ฐ์ฒด๋ฅผ Member ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
    Member member = mapper.memberPostToMember(post);
    
	  // ๋ณ€ํ™˜๋œ Member ๊ฐ์ฒด์— ID๋ฅผ ์„ค์ •
	  //์™œ๋ƒ๋ฉด createMember์—์„œ Id๋ฅผ ๋ฆฌํ„ดํ•ด์ฃผ๊ณ 
    // Id๊ฐ€ response์—์„œ๋„ Location header์— ํฌํ•จ์ด๋˜์–ด์•ผ ํ•จ
    
    member.setMemberId(1L);

    // memberService.createMember ๋ฉ”์„œ๋“œ๊ฐ€ 
    //์–ด๋–ค Member ๊ฐ์ฒด๋ฅผ ๋ฐ›๋”๋ผ๋„ ๋ฏธ๋ฆฌ ์„ค์ •ํ•œ member ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก mock ์„ค์ •
    given(memberService.createMember(Mockito.any(Member.class)))
            .willReturn(member);

    // post ๊ฐ์ฒด๋ฅผ JSON ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜
    String content = gson.toJson(post);

    // when ->  /v11/members์— POST ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ
    ResultActions actions = mockMvc.perform(post("/v11/members")
            .accept(MediaType.APPLICATION_JSON) // ์‘๋‹ต์„ JSON ํ˜•์‹์œผ๋กœ ๋ฐ›๊ฒ ๋‹ค๋Š” ์˜๋ฏธ.
            .contentType(MediaType.APPLICATION_JSON) // ์š”์ฒญ ๋ณธ๋ฌธ์ด JSON ํ˜•์‹์ด๋ผ๋Š” ๊ฒƒ
            .content(content)); // ์š”์ฒญ ๋ณธ๋ฌธ์— ๋ณ€ํ™˜๋œ JSON ๋ฌธ์ž์—ด์„ ํฌํ•จ์‹œํ‚จ๋‹ค.

    // then: ๊ฒฐ๊ณผ๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ๋ถ€๋ถ„
    actions.andExpect(status().isCreated())
            // ์‘๋‹ต ํ—ค๋”์˜ Location ๊ฐ’์ด "/v11/members/"๋กœ ์‹œ์ž‘ํ•˜๋Š”์ง€ ํ™•์ธ
            .andExpect(header().string("Location", is(startsWith("/v11/members/"))));
}

  1. @MockBean
    • Application Context์— ๋“ฑ๋ก๋˜์–ด ์žˆ๋Š” Bean์— ๋Œ€ํ•œ Mockito Mock ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ฃผ์ž…ํ•ด ์ฃผ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค
      โ†’ @MockBean ์• ๋„ˆํ…Œ์ด์…˜์„ ํ•„๋“œ์— ์ถ”๊ฐ€ํ•˜๋ฉด ํ•ด๋‹น ํ•„๋“œ์˜ Bean์— ๋Œ€ํ•œ Mock ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ ํ›„,
      ํ•„๋“œ์— ์ฃผ์ž…(DI)ํ•œ๋‹ค..
    • ์˜ˆ์ œ ์ฝ”๋“œ์—์„œ๋Š” MemberService ๋นˆ์— ๋Œ€ํ•œ Mock ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด์„œ memberService ํ•„๋“œ์— ์ฃผ์ž…ํ•œ๋‹ค.
  2. MemberMapper
    • DI ๋ฐ›๋Š” ์ด์œ ๋Š” MockMemberService(๊ฐ€์นญ)์˜ createMember()์—์„œ ๋ฆฌํ„ดํ•˜๋Š” Member ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•จ
      โ†’ post(MemberDto.Post ํƒ€์ž…) ๋ณ€์ˆ˜๋ฅผ Member ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜
    • new Member()์™€ ๊ฐ™์ด Member ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด๋„ ๋˜์ง€๋งŒ post ๋ณ€์ˆ˜๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด MemberMapper๋กœ ๋ณ€ํ™˜ ํ•จ.
    • ์‹ค์ œ createMember()์˜ ๋ฆฌํ„ด ๊ฐ’(Member ๊ฐ์ฒด)์—๋Š” memberId๊ฐ€ ํฌํ•จ์ด ๋˜๋Š”๋ฐ
      ์ด memberId๋Š” response์˜ Location header์— ํฌํ•จ์ด ๋˜์–ด์•ผ ํ•˜๋ฏ€๋กœ MockMemberService(๊ฐ€์นญ)์˜ createMember()์—์„œ๋„
      memberId๋ฅผ ๋ฆฌํ„ดํ•ด ์ค„ ์ˆ˜ ์žˆ๋„๋ก memberId๋ฅผ ์ถ”๊ฐ€ํ•จ.
  3. Stubbing ๋ฉ”์„œ๋“œ(Mockito์—์„œ ์ง€์›)

    1. ๐Ÿค”Stubbing์ด๋ž€? - ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด์„œ Mock ๊ฐ์ฒด๊ฐ€ ํ•ญ์ƒ ์ผ์ •ํ•œ ๋™์ž‘์„ ํ•˜๋„๋ก ์ง€์ •ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธ

    2. given()์€ Mock ๊ฐ์ฒด๊ฐ€ ํŠน์ • ๊ฐ’์„ ๋ฆฌํ„ดํ•˜๋Š” ๋™์ž‘์„ ์ง€์ •ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•˜๋ฉฐ,
      Mockito์—์„œ ์ง€์›ํ•˜๋Š” when()๊ณผ ๋™์ผํ•œ ๊ธฐ๋Šฅ์„ ํ•ฉ๋‹ˆ๋‹ค.

      ์—ฌ๊ธฐ์„œ๋Š” Mock ๊ฐ์ฒด์ธ memberService ๊ฐ์ฒด๋กœ createMember() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ์ •์˜ํ•˜๊ณ  ์žˆ๊ณ  , ํŒŒ๋ผ๋ฏธํ„ฐ์ธ Mockito.any(Member.class)๋Š” Mockito์—์„œ ์ง€์›ํ•˜๋Š” ๋ณ€์ˆ˜ ํƒ€์ž… ์ค‘ ํ•˜๋‚˜์ด๋‹ค.
      ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ์‹ค์ œ ๋ฉ”์„œ๋“œ์—์„œ ๋“ค์–ด๊ฐ€๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ํƒ€์ž…์„ ์ง€์ •ํ•ด์ฃผ๋ฉด ๋˜๋Š”๋ฐ Mockito.any( )์— ๊ฐ์‹ธ์„œ ๋„ฃ์–ด์ฃผ๋ฉด ๋จ.

      • ๊ฐ์ฒด์ผ ๊ฒฝ์šฐ๋Š” ์ƒ๊ธฐ ์ฝ”๋“œ์ฒ˜๋Ÿผ Mockito.any(๊ฐ์ฒด๋ช….class)
      • long type์˜ ๊ฒฝ์šฐ Mockito.anyLong()
      • int type์˜ ๊ฒฝ์šฐ Mockito.anyInt()
      • String type์˜ ๊ฒฝ์šฐ Mockito.anyString()
    3. .willReturn(member)

      MockMemberService(๊ฐ€์นญ)์˜ createMember() ๋ฉ”์„œ๋“œ๊ฐ€ ๋ฆฌํ„ดํ•  Stub ๋ฐ์ดํ„ฐ

    4. Debugging ํ•˜๊ณ  memberService๋ฅผ ๋ณด๋ฉด MockitpMock์œผ๋กœ ๊ฐ€์งœ ๊ฐ์ฒด๊ฐ€ ๋“ค์–ด์™€์žˆ๋‹ค.



์„œ๋น„์Šค ๋‹จ์œ„ํ…Œ์ŠคํŠธ์— Mockito ์ ์šฉ

  • ์ผ๋ฐ˜์ ์œผ๋กœ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์€ ๋ฐ์ดํ„ฐ ์•ก์„ธ์Šค ๊ณ„์ธต๊ณผ๋Š” ๋ฌด๊ด€ํ•˜๊ฒŒ ์„œ๋น„์Šค ๊ณ„์ธต์— ๊ตฌํ˜„๋œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ž์ฒด๋ฅผ Spring Framework์˜ ๋„์›€์„ ๋ฐ›์ง€ ์•Š๊ณ ๋„ ๋น ๋ฅด๊ฒŒ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์–ด์•ผํ•œ๋‹ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.springboot.member.service;

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

import java.util.Optional;

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

// ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์—์„œ Mockito๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ํ•„์š”ํ•œ ์–ด๋…ธํ…Œ์ด์…˜ 
//MockitoExtension ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•˜๊ฒ ๋‹ค๋Š” ์˜๋ฏธ
@ExtendWith(MockitoExtension.class) 
class MemberServiceMockTest {
    @Mock  // MockBean๊ณผ๋Š” ๋‹ค๋ฆ„. ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ์˜ ๋„์›€์ด ํ•„์š” ์—†์–ด์„œ ์‚ฌ์šฉ
    private MemberRepository memberRepository;

    @InjectMocks 
    // ํ…Œ์ŠคํŠธํ•ด์•ผ ํ•  ๊ฐ์ฒด์— ์ฃผ์ž…ํ•ด์•ผ ํ•œ๋‹ค. 
    //๊ฐ€์งœ Repository๋ฅผ ์˜์กดํ•˜๋Š” ์„œ๋น„์Šค๋ฅผ ์ฃผ์ž…ํ•˜๋Š” ๊ฒƒ.
    private MemberService memberService;

    @Test
    public void createMemberTest(){
        // given
        
        // ์ƒˆ๋กœ์šด Member ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
        Member member = new Member("jerry@gmail.com", "์ œ๋ฆฌ", "010-4111-1111");

        // when
        
        // memberRepository์˜ findByEmail ๋ฉ”์„œ๋“œ๊ฐ€ ์–ด๋–ค ๋ฌธ์ž์—ด์„ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›์•„๋„, 
        //ํ•ญ์ƒ Optional.of(member)๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
        given(memberRepository.findByEmail(Mockito.anyString())).willReturn(Optional.of(member));

        // then
        
        // createMember ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ–ˆ์„ ๋•Œ BusinessLogicException ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”์ง€ ํ™•์ธ
        // ์ด ํ…Œ์ŠคํŠธ๋Š” ์ด๋ฉ”์ผ์ด ์ค‘๋ณต๋˜๋Š” ๊ฒฝ์šฐ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”์ง€๋ฅผ ํ™•์ธํ•˜๋Š” ํ…Œ์ŠคํŠธ
        assertThrows(BusinessLogicException.class, () -> memberService.createMember(member));
    }
}

  • memberRepository Mock ๊ฐ์ฒด๋กœ Stubbing์„ ํ•˜๊ณ  ์žˆ์Œ.

    memberRepository.findByEmail(Mockito.anyString())์˜ ๋ฆฌํ„ด ๊ฐ’์œผ๋กœ
    Optional.of(member)๋ฅผ ์ง€์ •ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๊ฒฐ๊ณผ๋Š” โ€œpassedโ€์ด๋‹ค.

  • Optional.of(member)์˜ member ๊ฐ์ฒด์— ํฌํ•จ๋œ ์ด๋ฉ”์ผ ์ฃผ์†Œ๊ฐ€ memberService.createMember(member)์—์„œ
    ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌํ•œ member ๊ฐ์ฒด์— ํฌํ•จ๋œ ์ด๋ฉ”์ผ ์ฃผ์†Œ์™€ ๋™์ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฒ€์ฆ ๊ฒฐ๊ณผ๊ฐ€ โ€œpassedโ€

Comment

์ฒ˜์Œ์— ์ˆ˜์—…์ด ๋„ˆ๋ฌด ๋น ๋ฅด๊ฒŒ ์ง„ํ–‰๋˜์„œ ์ดํ•ด๋ฅผ ๋ชปํ•ด์„œ ์ฝ”๋“œ์— ํ•˜๋‚˜ ํ•˜๋‚˜ ์ฃผ์„๋‹ฌ๊ณ  ๋ฌผ์–ด๋ณด๊ณ ํ•ด์„œ ์กฐ๊ธˆ ์ดํ•ด๋˜์—ˆ๋Š”๋ฐ
๋ง‰์ƒ ๋˜ ์“ฐ๋ ค๊ณ  ํ•˜๋ฉด ๋‹ค ๋‹จ์ ˆ ์‹œํ‚ค๊ณ  ํ•ด์•ผํ•˜๋‹ˆ๊นŒ ์–ด๋–ป๊ฒŒ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด์•ผํ•˜๋Š” ๊ฑด์ง€๋Š” ์•„์ง ์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค..
ํŠนํžˆ ์ปจํŠธ๋กค๋Ÿฌ์˜ ๊ฒฝ์šฐ์—๋Š” ์ปจํŠธ๋กค๋Ÿฌ์˜ ๊ธฐ๋Šฅ๋งŒ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์—
๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ง„ํ–‰ํ•  ํ•„์š”๊ฐ€ ์—†๊ณ  ์‘๋‹ต๊ณผ ์š”์ฒญ์ด ์ž˜ ์ง„ํ–‰๋˜๋Š”์ง€๋ฅผ ํ…Œ์ŠคํŒ… ํ•˜๋Š” ๊ฒƒ์ด๋ผ๊ณ  ํ•œ๋‹คโ€ฆ.!
ํšŒ์‚ฌ์—์„œ๋„ ์ฃผ๋‹ˆ์–ด๋ถ„๋“ค์€ ์–ด๋ ค์›Œ์„œ Mockito๋ฅผ ์ž˜ ์•ˆํ•œ๋‹ค๊ณ  ๊ทธ๋งŒํผ ์–ด๋ ต๊ณ  ํž˜๋“ ๊ฑฐ ๋งž์œผ๋‹ˆ๊นŒ
๋ช‡์‹œ๊ฐ„์ด๋ผ๋„ ์ดํ•ดํ•˜๊ณ  ๋…ธ๋ ฅํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค๊ณ  ํ•˜์…”์„œ ์—ด์‹ฌํžˆ ๋ด์•ผ๊ฒ ๋‹ค๐Ÿ‘

Spring ์นดํ…Œ๊ณ ๋ฆฌ ๋‚ด ๋‹ค๋ฅธ ๊ธ€ ๋ณด๋Ÿฌ๊ฐ€๊ธฐ

Leave a comment