테스트

Mockito

내일도무사히 2021. 10. 19. 16:18

🍸Mockito 이란?

테스트를 위한 Mock를 구축할 수 있게 해주는 자바 진영의 프레임워크

🧩Mockito Syntax

스프링 환경에서 코드를 작성했다. 스프링부트에서는 mock 객체를 라이브러리로 지원하기에 별다른 추가 빌드 코드 없이 사용 할 수 있다.

 

 

Cook class - mockito에서 사용할 클래스이다. 실제 객체를 생각하고 만들었다.

 

1. mock(className.class)

        /**
     * mock(classToMock.class)로 mock 객체를 만들 수 있다.
     * 내부 구현은 동작하지 않고 0, null, false 등이 리턴된다.
     */
    @Test
    void createMockTest() {
        // given
        Cook mockCook = mock(Cook.class);

        // when
        mockCook.setOrderedItem("hamburger");

        // then
        assertThat(mockCook.getOrderedItem()).isEqualTo(null) ;

    }

mock 객체를 만드는 함수로, mock 함수를 사용해 mock을 만들 경우 내부 구현은 동작하지 않고, 내부 메소드를 호출시 0,null, false등이 호출된다.

 

2. Verify(mockClass, ... ).methodName()

        /**
     * verify: mock 객체에 어떤 api가 호출되었는지, 몇 번 호출되었는지 확인 가능
     */
    @Test
    void verifyTest() {
        Cook mockCook = mock(Cook.class);

        mockCook.getOrderedItem();
        // getOrderedItem이 한번이라도 호출되었는가?
        verify(mockCook).getOrderedItem();

        mockCook.getOrderedItem();
        mockCook.getOrderedItem();

        // getOrderedItem 총 3번 호출됨
        verify(mockCook, times(3)).getOrderedItem();
        // getOrderedItem 적어도 2번 이상 호출됨
        verify(mockCook, atLeast(2)).getOrderedItem();

    }

'methodName' 메소드가 몇 번이나 호출 되었는지 알 수 있도록 해주는 메소드다. mockClass 옆에 인자로 주어진 함수로 몇 번이나 호출되었는지 확인 할 수도 있다. times(n)는 n번 호출되었는지 확인하는 함수고, atLeast(n)는 n번 이상 호출되었는지 확인 할 수 있는 함수다.

 

output

 

3. When(mockMethodCall).thenReturn(something)

        /**
     * when: mock 객체가 어떤 값을 리턴하도록 만들 수 있음. 이것을 Stubbing이라고 한다.
     */
    @Test
    void whenTest() {
        //given
        Cook mockCook = mock(Cook.class);

        // when
        when(mockCook.getOrderedItem()).thenReturn("hamburger");

        // then
        assertThat(mockCook.getOrderedItem()).isEqualTo("hamburger");
    }

When 함수를 사용해 특정한 값을 리턴하도록 적용 할 수 있다. 위의 코드에서 적용된 함수는 mockCook.getOrderedItem()이 호출되면 "hamburger"를 리턴하겠다는 뜻이다. 이를 stubbing이라고 한다.

output

 

4. ArgumentCaptor

        /**
     * ArgumentCaptor: Mock 객체에 전달된 인자를 확인하는 용도로 사용
     */
    @Test
    void argumentCaptorTest() {
        Cook mockCook = mock(Cook.class);
        ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);

        mockCook.setOrderedItem("pizza");
        verify(mockCook).setOrderedItem(arg.capture());
        assertThat(arg.getValue()).isEqualTo("pizza");
    }

ArgumentCaptor를 사용해 mock 객체의 메소드에 어떤 인자들이 전달되었는지 확인 할 수 있다. 위의 코드를 보면 verify(mockCook).setOrderItem(arg.capture())라는 걸 볼 수 있는데, arg에 setOrderItem으로 전달되었던 데이터를 저장한다는 뜻이다.

output

 

5. Spy

        /**
     * Spying: Mock 객체는 내부 구현이 동작하지 않는것에 비해 Spy객체는 실제 객체처럼 동작하고
     * Verify로 어떤 메소드가 호출되었는지 확인 할 수있다.
     * 또한 When을 통해 일부 메소드를 stubbing 할 수 있다.
     */
    @Test
    void spyingCook() {
        Cook spy = spy(Cook.class);
        spy.setOrderedItem("hamburger");
        assertThat(spy.getOrderedItem()).isEqualTo("hamburger");
    }

mock 객체와는 다르게 실제 객체처럼 동작하는 클래스이다. 실제로 cook에 정의되어있는 set 메소드등이 정상적으로 작동하여 테스트를 통과하는 모습을 볼 수 있다.

output

 

⚠️ 주의사항

        @Test
    void spyingListError() {
        ArrayList spyList = spy(ArrayList.class);

        when(spyList.get(0)).thenReturn("apple");
        when(spyList.get(1)).thenReturn("banana");
        when(spyList.size()).thenReturn(10);

        assertThat(spyList.get(0)).isEqualTo("apple");
        assertThat(spyList.get(1)).isEqualTo("banana");
        assertThat(spyList.size()).isEqualTo(10);
    }

output

 

IndexOutOfBoundException 발생

spy는 실제 코드처럼 동작하기에 when라인의 get()함수를 사용 할 경우 저장되어 있는 값이 없어 에러를 발생 시키는 것을 볼 수 있다. 이는 다음과 같이 doReturn(value).when(spyObject).method() 패턴을 사용하면 고칠 수 있다.

        @Test
    void spyingListWhenFixed() {
        ArrayList spyList = spy(ArrayList.class);

        doReturn("apple").when(spyList).get(0);
        doReturn("banana").when(spyList).get(1);
        doReturn(10).when(spyList).size();

        assertThat(spyList.get(0)).isEqualTo("apple");
        assertThat(spyList.get(1)).isEqualTo("banana");
        assertThat(spyList.size()).isEqualTo(10);
    }

 

 

6. Exception Throw

        /**
     * 예외 발생 : when().thenThrow(ExceptionClass) 패턴 사용
     */
    @Test
    void whenGetUserInfoThrowException() {
        Cook mockCook = mock(Cook.class);
        when(mockCook.getOrderedItem()).thenThrow(NullPointerException.class);

        mockCook.getOrderedItem();
    }

when의 thenThrow(ExceptionClass) 를 활용해 예외를 발생시킬 수 있다.

output

NullPointerException이 발생한 것을 볼 수 있다.

발생한 예외를 테스트내에서 정상으로 표기하려면 다음과 같이 Junit5의 assertThrow를 사용하면 된다.

        /**
     * 예외 발생 : when().thenThrow(ExceptionClass) 패턴 사용
     * Junit5 사용시 assertThrow(Exception.class, () -> { dosomething() } 사용
     */
    @Test
    void whenGetUserInfoThrowException() {
        Cook mockCook = mock(Cook.class);
        when(mockCook.getOrderedItem()).thenThrow(NullPointerException.class);

        assertThrows(NullPointerException.class, () -> {
            mockCook.getOrderedItem();
        });
    }

output

 


References

https://www.baeldung.com/mockito-annotations

https://codechacha.com/ko/mockito-best-practice/

 

Java - Mockito를 이용하여 테스트 코드 작성하는 방법

Mockito는 Java에서 인기있는 Mocking framework입니다. Mockito로 객체를 mocking하여 Unit Test를 작성할 수 있습니다. 직접 Mock 객체를 만들 수 있지만 Mockito와 같은 Mocking framework을 사용하면 번거로운 코드를

codechacha.com