ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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

     

    '테스트' 카테고리의 다른 글

    Mock이란?  (0) 2021.10.19

    댓글

Designed by Tistory.