Mocking Framework - Mockito

Mockito๋Š” Java ๋‹จ์œ„ ํ…Œ์ŠคํŠธ(Unit Test) ์ž‘์„ฑ์„ ๋•๋Š” ๋ชจํ‚น(Mocking) ํ”„๋ ˆ์ž„์›Œํฌ๋กœ, ์‹ค์ œ ์˜์กด ๊ฐ์ฒด ๋Œ€์‹  ๊ฐ€์งœ ๊ฐ์ฒด(Mock Object)๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ œ์–ดํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.

  • ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ๊ฒฉ๋ฆฌ: ์™ธ๋ถ€ ์š”์ธ(DB, ๋„คํŠธ์›Œํฌ, ๋‹ค๋ฅธ ํด๋ž˜์Šค์˜ ๋‚ด๋ถ€ ๋กœ์ง ๋“ฑ)์— ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š๊ณ  ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์˜ ๋กœ์ง์—๋งŒ ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›

  • ํ–‰๋™ ์ œ์–ด ๋ฐ ๊ฒ€์ฆ: ์›ํ•˜๋Š” ๋ชจ๋“  ์ƒํ™ฉ์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜(Stubbing)ํ•˜๊ณ , ๋ฉ”์„œ๋“œ๊ฐ€ ์˜ˆ์ƒ๋Œ€๋กœ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€(Verification) ๊ฒ€์ฆํ•˜๋Š” ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณต

  • ๊ฐ€๋…์„ฑ ๋†’์€ ํ…Œ์ŠคํŠธ: BDD(Behavior-Driven Development) ์Šคํƒ€์ผ์„ ์ง€์›ํ•˜๋Š” ๋“ฑ, ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์˜ ์˜๋„๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›

1. Mock ๊ฐ์ฒด ์ƒ์„ฑ

์˜์กด์„ฑ์„ ๋Œ€์ฒดํ•  ๊ฐ€์งœ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€๋‹ค.

Mockito.mock() ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ

import static org.mockito.Mockito.mock;

class MemberServiceTest {

    MemberRepository mockMemberRepository = mock(MemberRepository.class);
}

@Mock ์–ด๋…ธํ…Œ์ด์…˜ ์‚ฌ์šฉ

ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค ์ƒ๋‹จ์— @ExtendWith(MockitoExtension.class)๋ฅผ ์„ ์–ธํ•˜์—ฌ Mockito ํ™•์žฅ ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™”ํ•œ ํ›„, ํ•„๋“œ์— @Mock ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ ์‚ฌ์šฉํ•œ๋‹ค.

  • @Mock: ํ•ด๋‹น ํ•„๋“œ๋ฅผ Mock ๊ฐ์ฒด๋กœ ์ดˆ๊ธฐํ™”

  • @InjectMocks: ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ , @Mock ๋˜๋Š” @Spy๋กœ ์ƒ์„ฑ๋œ ์˜์กด ๊ฐ์ฒด๋ฅผ ์ž๋™์œผ๋กœ ์ฃผ์ž…


@ExtendWith(MockitoExtension.class)
class MemberServiceTest {

    @Mock
    private MemberRepository memberRepository; // MemberRepository์˜ Mock ๊ฐ์ฒด๊ฐ€ ์ฃผ์ž…

    @InjectMocks
    private MemberService memberService; // ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ๊ฐ์ฒด, @Mock ๊ฐ์ฒด์ธ memberRepository๊ฐ€ ์ฃผ์ž…

}

@Spy - ์‹ค์ œ ๊ฐ์ฒด ์ผ๋ถ€ Mocking

์‹ค์ œ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ํŠน์ • ๋ฉ”์„œ๋“œ์˜ ํ–‰๋™๋งŒ ๋ณ€๊ฒฝํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.(Stubbing ํ•˜์ง€ ์•Š์€ ๋ฉ”์„œ๋“œ๋Š” ์‹ค์ œ ๋กœ์ง์„ ์ˆ˜ํ–‰)


@Spy
private List<String> spiedList = new ArrayList<>();

@Test
void spyTest() {
    // ์‹ค์ œ add ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋จ
    spiedList.add("one");

    // size() ๋ฉ”์„œ๋“œ๋งŒ Stubbing
    doReturn(100).when(spiedList).size();

    assertEquals(100, spiedList.size()); // Stubbing๋œ ๊ฐ’ ๋ฐ˜ํ™˜
    assertEquals("one", spiedList.get(0)); // ์‹ค์ œ get ๋ฉ”์„œ๋“œ ๋™์ž‘
}

2. Stubbing - ํ–‰๋™ ์ •์˜

Mock ๊ฐ์ฒด๊ฐ€ ํŠน์ • ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์— ์–ด๋–ป๊ฒŒ ์‘๋‹ตํ• ์ง€ ๋ฏธ๋ฆฌ ์ •์˜ํ•˜๋Š” ๊ณผ์ •์ด๋‹ค.

when() ๊ณ„์—ด

๋ฐ˜ํ™˜๊ฐ’์ด ์žˆ๋Š” ๋ฉ”์„œ๋“œ์˜ ํ–‰๋™์„ ์ •์˜ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

  • thenReturn(value): ๊ณ ์ •๋œ ๊ฐ’์„ ๋ฐ˜ํ™˜

  • thenThrow(exception): ์˜ˆ์™ธ ๋ฐœ์ƒ

  • thenAnswer(answer): ๋™์ ์ธ ๋กœ์ง์„ ํ†ตํ•ด ๊ณ„์‚ฐ๋œ ๊ฐ’์„ ๋ฐ˜ํ™˜


@Test
void exmaple() {

    // findById(1L) ํ˜ธ์ถœ ์‹œ member ๊ฐ์ฒด ๋ฐ˜ํ™˜
    when(memberRepository.findById(1L))
            .thenReturn(Optional.of(member));

    // findById(2L) ํ˜ธ์ถœ ์‹œ ์˜ˆ์™ธ ๋ฐœ์ƒ
    when(memberRepository.findById(2L))
            .thenThrow(new RuntimeException("Member not found"));

    // ์—ฐ์† ํ˜ธ์ถœ์— ๋Œ€ํ•ด ๋‹ค๋ฅธ ํ–‰๋™ ์ •์˜
    when(mock.someMethod())
            .thenReturn("first call")
            .thenReturn("second call");
    
    // ๋™์ ์ธ ์‘๋‹ต ์ƒ์„ฑ
    when(mock.calculate(anyInt()))
            .thenAnswer(invocation -> {
                int arg = invocation.getArgument(0);
                return arg * 2;
            });
}

do*() ๊ณ„์—ด

๋ฐ˜ํ™˜๊ฐ’์ด void์ธ ๋ฉ”์„œ๋“œ๋‚˜, ์‹ค์ œ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์„ ํ”ผํ•˜๋ฉด์„œ Stubbing ํ•ด์•ผ ํ•˜๋Š” Spy ๊ฐ์ฒด์— ์‚ฌ์šฉ๋œ๋‹ค.

  • doNothing(): ์•„๋ฌด ๋™์ž‘๋„ ํ•˜์ง€ ์•Š์Œ

  • doThrow(exception): ์˜ˆ์™ธ ๋ฐœ์ƒ

  • doAnswer(answer): ๋™์ ์ธ ๋กœ์ง์„ ์‹คํ–‰

  • doReturn(value): when() ๋Œ€์‹  ์‚ฌ์šฉํ•˜๋Š” Stubbing(Spy ๊ฐ์ฒด์— ๊ถŒ์žฅ)


@Test
void exmaple() {
    // delete(member)๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด ์•„๋ฌด ๋™์ž‘๋„ ํ•˜์ง€ ์•Š๋„๋ก ์ •์˜
    doNothing()
            .when(memberRepository)
            .delete(member);

    // delete(member)๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด ์˜ˆ์™ธ ๋ฐœ์ƒ
    doThrow(new IllegalArgumentException())
            .when(memberRepository)
            .delete(member);
}

3. Verification - ํ–‰์œ„ ๊ฒ€์ฆ

ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ๋กœ์ง์ด ์‹คํ–‰๋œ ํ›„, Mock ๊ฐ์ฒด์˜ ๋ฉ”์„œ๋“œ๊ฐ€ ์˜ˆ์ƒ๋Œ€๋กœ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ๊ฒ€์ฆํ•œ๋‹ค.

  • times(n): ์ •ํ™•ํžˆ n๋ฒˆ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ๊ฒ€์ฆ

  • verify(mock): ํ•ด๋‹น ๋ฉ”์„œ๋“œ๊ฐ€ 1๋ฒˆ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ๊ฒ€์ฆ(= times(1))

  • never(): ํ•œ ๋ฒˆ๋„ ํ˜ธ์ถœ๋˜์ง€ ์•Š์•˜๋Š”์ง€ ๊ฒ€์ฆ(= times(0))

  • atLeast(n) / atMost(n): ์ตœ์†Œ n๋ฒˆ / ์ตœ๋Œ€ n๋ฒˆ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ๊ฒ€์ฆ

  • inOrder(mock...): ์—ฌ๋Ÿฌ Mock ๊ฐ์ฒด์— ๊ฑธ์ณ ํ˜ธ์ถœ ์ˆœ์„œ๊ฐ€ ์ •ํ™•ํ•œ์ง€ ๊ฒ€์ฆ

  • timeout(millis): ๋น„๋™๊ธฐ ์ฝ”๋“œ ํ…Œ์ŠคํŠธ ์‹œ, ์ง€์ •๋œ ์‹œ๊ฐ„ ๋‚ด์— ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๋Š”์ง€ ๊ฒ€์ฆ


@Test
void exmaple() {
    // deleteById(1L)๊ฐ€ ์ •ํ™•ํžˆ 1๋ฒˆ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ๊ฒ€์ฆ
    verify(memberRepository, times(1))
            .deleteById(1L);

    // deleteById(2L)๋Š” ํ˜ธ์ถœ๋˜์ง€ ์•Š์•˜๋Š”์ง€ ๊ฒ€์ฆ
    verify(memberRepository, never())
            .deleteById(2L);

    // ๋น„๋™๊ธฐ ์ž‘์—… ํ›„ 100ms ์•ˆ์— notify ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ๊ฒ€์ฆ
    verify(notificationService, timeout(100))
            .notify(any(User.class));

    // ์ˆœ์„œ ๊ฒ€์ฆ
    InOrder inOrder = inOrder(memberRepository, emailService);
    inOrder.verify(memberRepository)
            .save(any(Member.class)); // save๊ฐ€ ๋จผ์ €
    inOrder.verify(emailService)
            .sendWelcomeEmail(any(Member.class)); // email ๋ฐœ์†ก์ด ๋‚˜์ค‘์—
}

4. Argument Matchers & Captors

Argument Matchers

Stubbing์ด๋‚˜ ๊ฒ€์ฆ ์‹œ, ์ธ์ž์˜ ์‹ค์ œ ๊ฐ’ ๋Œ€์‹  ์œ ์—ฐํ•œ ์กฐ๊ฑด์„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์„ ๋•Œ ํ™œ์šฉํ•œ๋‹ค.

  • any(): ๋ชจ๋“  ํƒ€์ž…์˜ ๊ฐ์ฒด๋ฅผ ํ—ˆ์šฉ(anyString(), anyInt() ๋“ฑ ํƒ€์ž…๋ณ„ Matcher ์ œ๊ณต)

  • eq(value): ํŠน์ • ๊ฐ’๊ณผ ๋™์ผํ•ด์•ผ ํ•จ์„ ๋ช…์‹œ

  • argThat(matcher): ์ปค์Šคํ…€ Matcher๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ณต์žกํ•œ ์กฐ๊ฑด ์ง€์ • ๊ฐ€๋Šฅ

์ค‘์š”ํ•œ ์ ์€ ๋ฉ”์„œ๋“œ์˜ ์—ฌ๋Ÿฌ ์ธ์ž ์ค‘ ํ•˜๋‚˜๋ผ๋„ Argument Matcher๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค๋ฉด, ๋ชจ๋“  ์ธ์ž๋ฅผ Matcher๋กœ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.


@Test
void exmaple() {

    // eq()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ผ๋ฐ˜ ๊ฐ’์„ Matcher๋กœ ๋ณ€ํ™˜
    when(memberRepository.save(eq("user"), anyInt()))
            .thenReturn(member);
}

Last updated

Was this helpful?