본문 바로가기
Java

[Java] Mockito 사용법 (3) - 스터빙 (Stubbing) (OngoingStubbing, Stubber)

by 노력남자 2022. 9. 6.
반응형

이번 포스팅에선 스터빙(Stubbing)의 개념과  Mockito에서 스터빙을 하는 방법에 대해 알아보겠습니다.

 

스터빙(Stubbing)이란?

 

Mockito에서 스터빙을 알아보기 전에 개념부터 알아보겠습니다.

 

Stubbing은 stub이라고도 하며 토막, (표수표 등에서 한 쪽을 떼어 주고) 남은 부분이라는 뜻을 가지고 있습니다.

 

위키피디아에 정의된 내용은 다음과 같습니다. (https://en.wikipedia.org/wiki/Test_stub)

 

테스트 스텁(Test Stub)은 테스트 호출 중 테스트 스텁은 테스트 중에 만들어진 호출에 대해 미리 준비된 답변을 제공하는 것

 

만들어진 mock 객체의 메소드를 실행했을 때 어떤 리턴 값을 리턴할지를 정의하는 것이라고 생각하시면 됩니다.

 

Mockito에선 어떻게 스터빙을 할까?

 

Mockito에선 when 메소드를 이용해서 스터빙을 지원하고 있습니다.

 

when에 스터빙할 메소드를 넣고 그 이후에 어떤 동작을 어떻게 제어할지를 메소드 체이닝 형태로 작성하면됩니다.

 

스터빙을 할 수 있는 방법은 OngoinStubbing, Stubber를 쓰는 방법 2가지가 있습니다. 하나씩 살펴보겠습니다.

 

OngoingStubbing 메소드

 

OngoingStubbing 메소드란 when에 넣은 메소드의 리턴 값을 정의해주는 메소드입니다. (OngoingStubbing = "진행 중인 스터빙"이라는 뜻입니다.)

 

when({스터빙할 메소드}).{OngoingStubbing 메소드};

 

메소드명 설명
thenReturn 스터빙한 메소드 호출 후 어떤 객체를 리턴할 건지 정의
thenThrow 스터빙한 메소드 호출 후 어떤 Exception을 Throw할 건지 정의
thenAnswer 스터빙한 메소드 호출 후 어떤 작업을 할지 custom하게 정의

mockito javadoc을 보면 이 메소드를 굳이 사용하지 말고 thenReturn, thenThrow 메소드 사용을 추천하고 있습니다.
thenCallRealMethod 실제 메소드 호출

 

예제

 

테스트를 위해 ProductService 클래스를 만들겠습니다.

 

public class ProductService {

    public Product getProduct() {
        return new Product("A001", "monitor");
    }

    public Product getProduct(String serial, String name) {
        return new Product(serial, name);
    }
}

 

@ExtendWith(MockitoExtension.class)
public class OngoingStubbingMethod {

    @Mock
    ProductService productService;

    @Test
    void testThenReturn() {
        Product product = new Product("T001", "mouse");

        when(productService.getProduct()).thenReturn(product);

        assertThat(productService.getProduct()).isEqualTo(product);
    }

    @Test
    void testThenThrows() {
        when(productService.getProduct()).thenThrow(new IllegalArgumentException());

        assertThatThrownBy(() -> productService.getProduct()).isInstanceOf(IllegalArgumentException.class);
    }

    @Test
    void testThenAnswer() {
        when(productService.getProduct(any(), any())).thenAnswer((Answer) invocation -> {
            Object[] args = invocation.getArguments();

            return new Product(args[0] + "1", args[1] + "_1233");
        });

        assertThat(productService.getProduct("S001","desk").getSerial()).isEqualTo("S0011");
    }

    @Test
    void testThenCallRealMethod() {
        when(productService.getProduct()).thenCallRealMethod();

        assertThat(productService.getProduct().getSerial()).isEqualTo("A001");
    }
}

 

결과

 

 

Stubber 메소드

 

{Stubber 메소드}.when({스터빙할 클래스}).{스터빙할 메소드}

 

메소드명 설명
doReturn 스터빙 메소드 호출 후 어떤 행동을 할 건지 정의
doThrow 스터빙 메소드 호출 후 어떤 Exception을 throw할  건지 정의
doAnswer 스터빙 메소드 호출 후 작업을 할지 custom하게 정의
doNothing 스터빙 메소드 호출 후 어떤 행동도 하지 않게 정의
doCallRealMethod 실제 메소드 호출

 

Stubber 메소드는 OngoingStubbing과 다르게 when에 스터빙할 클래스를 넣고 그 후에 메소드를 호출합니다.

 

그 이유는 스터빙이 반드시 실행되야 하는 경우 사용하는 메소드이기 때문입니다.

 

아래 예를 들어놓겠습니다.

 

//--Ex1)
List list = new LinkedList();
List spy = spy(list);

//불가능 : list가 빈 객체이기 때문에 .get(0)을 할 때 IndexOutOfBoundsException을 발생하여 foo를 리턴하지 못 한다.
when(spy.get(0)).thenReturn("foo");

//위와 같은 경우를 doReturn을 사용한다.
doReturn("foo").when(spy).get(0);


//--Ex2)
when(mock.foo()).thenThrow(new RuntimeException());

//불가능 : 이미 mock.foo()를 호출할 때 RuntimeException이 일어나기 때문에 bar를 리턴할 수 없음
when(mock.foo()).thenReturn("bar");

//위와 같은 경우를 doReturn을 사용한다.
doReturn("bar").when(mock).foo();

 

또한, 리턴 타입인 void 메소드 테스트가 가능해집니다.

 

when 메소드는 인자로 T를 받는데 void 메소드는 리턴 값이 없어서 사용할 수 없습니다.

 

그래서 Stubber 메소드를 사용해야 합니다.



예제

 

테스트를 위해 UserService 클래스를 만들겠습니다.

 

public class UserService {

    public User getUser() {
        return new User("effortguy", "1234");
    }

    public int getLoginErrNum() {
        return 1;
    }
    
    public void deleteUser() {}
}

 

@ExtendWith(MockitoExtension.class)
public class StubberMethod {

    @Mock
    UserService userService;

    @Test
    void testDoReturn() {
        User user = new User("badguy", "4312");

        doReturn(user).when(userService).getUser();

        assertThat(userService.getUser()).isEqualTo(user);
    }

    @Test
    void testDoThrow() {
        doThrow(new RuntimeException()).when(userService).deleteUser();

        assertThatThrownBy(() -> userService.deleteUser()).isInstanceOf(RuntimeException.class);
    }
}

 

결과

 

 

메소드 호출마다 바뀌는 스터빙하기

 

OngoingStubbing 메소드를 메소드 체이닝으로 쓰면 메소드 호출마다 다른 스터빙을 호출할 수 있습니다.

 

예제

 

@ExtendWith(MockitoExtension.class)
public class ConsecutiveStubbing {

    @Mock
    ProductService productService;

    @Test
    void testConsecutiveStubbing() {
        Product product = new Product("D001","water");

        when(productService.getProduct())
                .thenReturn(product)
                .thenThrow(new RuntimeException());

        //첫 번째 호출 : .thenReturn(product)
        assertThat(productService.getProduct()).isEqualTo(product);

        //두 번째 호출 : .thenThrow(new RuntimeException());
        assertThatThrownBy(() -> productService.getProduct()).isInstanceOf(RuntimeException.class);
    }
}

 

결과

 

 

다음 포스팅에선 생성한 mock 객체를 verify하는 방법에 대해 알아보겠습니다.

 

읽어주셔서 감사합니다.

반응형

댓글