CH02 Mockito

Mockito 에 λŒ€ν•œ μ§€κΈˆμ˜ 생각(κ²°λ‘ : μ΅œλŒ€ν•œ μ“°μ§€ 말자)

이 κ°•μ˜ 자체λ₯Ό κ½€ μ˜ˆμ „μ— λ“€μ—ˆλ˜ 것이고, μ•„λž˜ λ…ΈνŠΈλ„ κ·Έ λ•Œ μ •λ¦¬ν–ˆλ˜ 것이닀. λ‹Ήμ‹œμ—λŠ” ν…ŒμŠ€νŠΈλ₯Ό ν•  λ•Œ ν•„μš”ν•œ λΆ€λΆ„μ—μ„œ mock 객체 등을 ν™œμš©ν•΄μ„œ ν…ŒμŠ€νŠΈλ₯Ό 더 μ‰½κ²Œ ν•  수 μžˆμ–΄μ„œ μ’‹λ‹€κ³  μƒκ°ν–ˆλŠ”λ° 사싀 μ§€κΈˆμ€ 생각이 μ’€ λ°”λ€Œμ—ˆλ‹€.

mock 을 μ΄μš©ν•΄μ„œ μ–΄λ– ν•œ 객체의 행동을 μ‘°μž‘ν•œλ‹€λŠ” 것 μžμ²΄κ°€ μ‘°μž‘ λΉ„μš©μ΄ λ„ˆλ¬΄ 큰 일이닀. λ­”κ°€κ°€ λ°”λ€Œλ©΄ 이 μ‘°μž‘κΉŒμ§€ λͺ¨λ‘ λ°”κΏ”μ€˜μ•Όν•˜λŠ”λ°, μ‘°μž‘μ΄ 클 수둝 μˆ˜μ • λΉ„μš©μ΄ 맀우 크닀. λ‚΄κ°€ μ§œλ“ , λ‹€λ₯Έ μ‚¬λžŒμ΄ μ§œλ†“μ€ μ½”λ“œλ“  ν…ŒμŠ€νŠΈ ν•˜κ³ μž ν•˜λŠ” λͺ¨λ“ˆ ν˜Ήμ€ λ©”μ†Œλ“œκ°€ ν”ΌμΉ˜ λͺ»ν•˜κ²Œ κ½€ λ§Žμ€ 일듀을 ν•˜λ„λ‘ μ²˜λ¦¬ν•΄ λ‘” κ²½μš°μ— μ–΄λ–€ μ˜λ„λœ ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•΄μ„œ 쀑간에 μ‘°μž‘μ„ ν•  게 λ„ˆλ¬΄ λ§Žλ‹€.

그런데 λ‚΄κ°€ λ§ν•˜κ³ μž ν•˜λŠ” 것은 λͺ¨ν‚Ή μžμ²΄κ°€ μˆ˜μ •λΉ„μš©μ΄ ν¬λ‹ˆκΉŒ μ•ˆμ’‹λ‹€λŠ” 이야기λ₯Ό ν•˜λ €λŠ” 것이 μ•„λ‹ˆκ³  μ• μ΄ˆμ— λͺ¨ν‚Ήμ„ 많이 ν•΄μ•Όν•  μ •λ„λ‘œ ν…ŒμŠ€νŠΈ λ‹¨μœ„κ°€ μ»€μ„œλŠ” μ•ˆλ˜κ³ , 그러기 μœ„ν•΄μ„œλŠ” μ„€κ³„μ‹œ 객체의 μ±…μž„κ³Ό μ—­ν•  λ²”μœ„κ°€ μ΅œλŒ€ν•œ μž‘μ•„μ•Όν•œλ‹€λŠ” 것이닀.

객체의 ν˜‘λ ₯ ꡬ쑰λ₯Ό μ΅œλŒ€ν•œ μͺΌκ°œμ„œ 섀계λ₯Ό 해두면 λ­”κ°€ νŠΉμ • λ‹¨μœ„μ˜ ν…ŒμŠ€νŠΈλ₯Ό ν•  λ•Œ ν•„μš”ν•œ μ‘°μž‘ ν–‰μœ„κ°€ 맀우 μ μ–΄μ§€κ±°λ‚˜ μ•„μ˜ˆ ν•„μš”κ°€ μ—†μ–΄μ§„λ‹€. 그리고 Mockito λ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šκ³  ν…ŒμŠ€νŠΈ μŠ€μ½”ν”„μ—μ„œ DIP λ₯Ό μ μš©ν•  μˆ˜λ„ μžˆλ‹€. κ·Έλ ‡κ²Œ 섀계λ₯Ό 잘 해두면 λœλ‹€.

μ•žμœΌλ‘œ 마주칠 κΈ°μ‘΄ λ ˆκ±°μ‹œ ν”„λ‘œμ νŠΈ ν˜Ήμ€ λ‚΄κ°€ λ§Œλ“œλŠ” μƒˆλ‘œμš΄ ν”„λ‘œμ νŠΈ λͺ¨λ‘ μ–΄λ–»κ²Œ λ μ§€λŠ” λͺ¨λ₯΄κ² κ³ , λ‚΄ 생각도 μ–΄λ–»κ²Œ λ°”λ€”μ§€ λͺ¨λ₯΄κ² μ§€λ§Œ μ§€κΈˆμœΌλ‘œμ„œλŠ” Mockito 자체λ₯Ό κ·Έλƒ₯ μ“Έ 일이 μ—†κ²Œλ” 섀계뢀터 μ΅œλŒ€ν•œ μ‹ κ²½μ“°μžλŠ” 것이 μ§€κΈˆμ˜ λ‚˜μ˜ 생각이닀.

Mockito μ†Œκ°œ

mock

mock 단어 μžμ²΄κ°€ 'κ°€μ§œμ˜' 와 같은 λœ»μ„ κ°€μ§€κ³  μžˆλ‹€. ν…ŒμŠ€νŠΈμ—μ„œ 'λͺ© 객체' 와 같은 λ‹¨μ–΄λ‘œ 자주 μ‚¬μš©λ˜λŠ” κ°œλ…μ΄λ‹€. ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•œ λ”λ―Έμ˜ κ°œλ…μ΄λ‹€.

곡식 ν™ˆνŽ˜μ΄μ§€ μ†Œκ°œ κ·ΈλŒ€λ‘œλ₯Ό μΈμš©ν•œλ‹€.

Tasty mocking framework for unit tests in Java

Mock 객체 λ§Œλ“œλŠ” 방법

Mockito.mock() λ©”μ†Œλ“œλ‘œ λ§Œλ“œλŠ” 방법

MemberService memberService = mock(MemberService.class);
StudyRepository studyRepository = mock(StudyRepository.class);

@Mock μ• λ…Έν…Œμ΄μ…˜μœΌλ‘œ λ§Œλ“œλŠ” 방법

  • JUnit 5 extension으둜 MockitoExtension을 μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.

  • λ©”μ†Œλ“œ λ§€κ°œλ³€μˆ˜μ—μ„œ μ„ μ–Έν•˜μ—¬λ„ 되고 클래슀의 멀버 ν•„λ“œλ‘œλ„ μ„ μ–Έν•˜μ—¬ μ‚¬μš©μ΄ κ°€λŠ₯ν•˜λ‹€.

@ExtendWith(MockitoExtension.class)
class SocialLoginServiceTest {

    // @Mock SocialLoginService socialLoginService  <- 클래슀의 멀버 ν•„λ“œλ‘œλ„ κ°€λŠ₯

    @DisplayName("μ–΄λ…Έν…Œμ΄μ…˜μ„ μ΄μš©ν•œ mock 객체 생성 ν…ŒμŠ€νŠΈ")
    @Test
    void createSocialLoginWithAnnotation(@Mock SocialLoginService socialLoginService) {
        assertNotNull(socialLoginService);
    }
}

Mock 객체 μ‘°μž‘ν•˜κΈ°

μΈν„°νŽ˜μ΄μŠ€μ™€ 같이 λ‹€ν˜•μ„±μ„ μœ„ν•΄μ„œ μΆ”μƒν™”λœ 클래슀λ₯Ό ν…ŒμŠ€νŠΈμ—μ„œ μ‚¬μš©ν•΄μ•Ό ν•  κ²½μš°κ°€ μžˆλ‹€. 뿐만 μ•„λ‹ˆλΌ μ™ΈλΆ€ api와 연동해야 ν•  경우, μ›ν•˜λŠ” κ²°κ³Όκ°’μ˜ μˆ˜μ‹ μ΄ λ˜μ—ˆλ‹€κ³  κ°€μ •ν•˜κ³  κ·Έ λ‹€μŒμ˜ 행동듀을 ν•˜λ‚˜μ˜ μœ λ‹›μœΌλ‘œ 보고 이λ₯Ό ν…ŒμŠ€νŠΈ ν•  λ•Œ, 값을 μ œλŒ€λ‘œ λ°›μ•˜λ‹€κ³  κ°€μ •ν•΄μ•Ό ν•  ν•„μš”κ°€ μžˆλ‹€. 이럴 λ•Œ, Mock객체λ₯Ό μ‘°μž‘ν•˜λ©΄ μ›ν•˜λŠ” ν…ŒμŠ€νŠΈ μ‹œλ‚˜λ¦¬μ˜€λŒ€λ‘œ μœ λ‹› ν…ŒμŠ€νŠΈλ₯Ό ν•˜κΈ°κ°€ μš©μ΄ν•΄μ§„λ‹€.

    @DisplayName("social login μ‹œ μ™ΈλΆ€ apiν†΅ν•œ 토큰 μˆ˜μ‹ ")
    @Test
    void socialLoginGetToken(@Mock SocialLoginService socialLoginService) {
        String token = "userToken";
        Mockito.when(socialLoginService.getToken()).thenReturn(token);
        assertEquals(token, socialLoginService.getToken());
    }

μ›λ¦¬λŠ” Mockito.when~ μ—μ„œ μ •μ˜ ν•΄λ‘”λŒ€λ‘œ 결과값이 미리 μ„ΈνŒ…μ΄λ˜κ³  μ‹€μ œλ‘œ ν•΄λ‹Ή μ½”λ“œκ°€ 싀행이 λ˜μ§€λ§Œ 결과값은 미리 μ„ΈνŒ…λœ 결과값을 받도둝 ν•˜λŠ” 것이닀. 즉, μ‹€μ œλ‘œ socialLoginService의 getToken()κ°€ 싀행이 되긴 ν•˜λŠ”λ° κ·Έκ²ƒμ˜ κ²°κ³Όκ°’μœΌλ‘œ 미리 μ •μ˜ν•΄λ‘” String token = β€œuserToken”; 값을 return λ°›κ²Œ λ˜λŠ” 것이닀.

이렇듯 미리 μ •μ˜ν•œ μ•½μ†λŒ€λ‘œ λ“€μ–΄μ˜¬ 경우 미리 μ •μ˜ν•œ 값을 return ν•˜λŠ” 방식이기 λ•Œλ¬Έμ—, νŒŒλΌλ―Έν„°κ°€ 사전에 μ •μ˜ν•˜μ§€ μ•Šμ€ λ‹€λ₯Έ 것이 였면 μ‹€μ œλ‘œ λ‘œμ§λŒ€λ‘œ μ²˜λ¦¬ν•œ κ²°κ³Όκ°€ return λœλ‹€. λ§Œμ•½ νŒŒλΌλ―Έν„°μ— κ·€μ†λ˜μ§€ μ•Šκ³  mock 객체λ₯Ό μ‚¬μš©ν•˜κ³  μ‹Άλ‹€λ©΄ μ•„λž˜μ™€ 같이 any()λ₯Ό μ‚¬μš©ν•˜λ©΄ λœλ‹€.

μΆ”κ°€λ‘œ, doThrow()λ₯Ό μ΄μš©ν•˜λ©΄ 미리 μ •μ˜ν•œ 값을 return λ°›λŠ” 것이 μ•„λ‹ˆλΌ 미리 μ •μ˜ν•œ μ—λŸ¬κ°€ λ°œμƒν•˜κ²Œλ” mock 객체λ₯Ό ν™œμš©ν•  μˆ˜λ„ μžˆλ‹€.

doThrow(new RuntimeException()).when(mockedList).clear();

μ•„λž˜λŠ” mokito의 ArgumentMatchers κ°€ κ°€μ§„ any()λ₯Ό ν†΅ν•΄μ„œ 더 νŽΈν•˜κ²Œ ν…ŒμŠ€νŠΈκ°€ κ°€λŠ₯ν•˜λ‹€.

    @DisplayName("social login μ‹œ μ™ΈλΆ€ apiν†΅ν•œ 토큰 μˆ˜μ‹ ")
    @Test
    void socialLoginGetToken(@Mock SocialLoginService socialLoginService) {
        String token = "userToken";
        Mockito.when(socialLoginService.getToken(any())).thenReturn(token);

        Object userInfo = new Object();
        assertEquals(token, socialLoginService.getToken(userInfo));
        assertEquals(token, socialLoginService.getToken("anything"));
    }

BDD μŠ€νƒ€μΌ Mockito API

BDD μŠ€νƒ€μΌλ‘œ Mokitoλ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€. BDD의 κΈ°λ³Έ 철학은 κ°œλ°œμžμ™€ λΉ„κ°œλ°œμžκ°„μ˜ collaboration 을 적극 μ‘μ›ν•œλ‹€λŠ” 것인데 μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ β€˜ν–‰λ™β€™μ„ μ†Œν†΅μ˜ κΈ°μ€€μœΌλ‘œ μ‚ΌλŠ”λ‹€. 즉, β€˜μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ μ–΄λ–»κ²Œ 행동해야 ν•œλ‹€β€™ λΌλŠ” μΈ‘λ©΄μ—μ„œ μ—¬λŸ¬κ°€μ§€λ₯Ό κ·œμ •ν•  수 있고 이 β€˜ν–‰λ™β€™μ„ λ°”νƒ•μœΌλ‘œ ν˜„μž¬ ν˜Ήμ€ κ°œλ°œμ€‘μΈ μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ μ μ ˆν•˜κ²Œ β€˜ν–‰λ™β€™ν•˜κ³  μžˆλŠ”μ§€λ₯Ό λ…Όμ˜ν•  수 μžˆλŠ” 것이닀.

κ·Έλž˜μ„œ BDD에 μž…κ°ν•˜μ—¬ μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ 행동을 κ·œμ •ν•  λ•Œ β€˜μ–΄λ–€ μƒν™©μ—μ„œ, μ–΄λ–€ κ²½μš°μ—, μ΄λ ‡κ²Œ ν–‰λ™ν•΄μ•Όν•œλ‹€β€™ 라고 μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ 행동을 κΈ°λŒ€ν•  수 μžˆλ‹€. 그리고 μ΄λŸ¬ν•œ κΈ°λŒ€λ₯Ό Mokitoλ₯Ό μ΄μš©ν•΄μ„œ μ½”λ“œλ‘œ ν‘œν˜„ν•˜μžλ©΄ λŒ€ν‘œμ μΈ BDD 포맷으둜 given, when, then 을 μ‚¬μš©ν•  수 μžˆλ‹€. μ•„λž˜λŠ” κ·Έ μ˜ˆμ‹œμ΄λ‹€.

given(memberService.findById(1L)).willReturn(Optional.of(member));
given(studyRepository.save(study)).willReturn(study);

μ•„λž˜λŠ” κ°•μ˜ μ†ŒμŠ€μ—μ„œ μ‚¬μš©λœ given, when, then μ˜ˆμ‹œμ΄λ‹€.

    @Test
    void createNewStudy() {
        System.out.println("========");
        System.out.println(port);

        // Given
        StudyService studyService = new StudyService(memberService, studyRepository);
        assertNotNull(studyService);

        Member member = new Member();
        member.setId(1L);
        member.setEmail("keesun@email.com");

        Study study = new Study(10, "ν…ŒμŠ€νŠΈ");

        given(memberService.findById(1L)).willReturn(Optional.of(member));

        // When
        studyService.createNewStudy(1L, study);

        // Then
        assertEquals(1L, study.getOwnerId());
        then(memberService).should(times(1)).notify(study);
        then(memberService).shouldHaveNoMoreInteractions();
    }

Last updated