이번 포스팅에선 스터빙(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하는 방법에 대해 알아보겠습니다.
읽어주셔서 감사합니다.
'Java' 카테고리의 다른 글
[Java] Mockito 사용법 (5) - BDD 스타일 API (+ BDD란?) (1) | 2022.09.06 |
---|---|
[Java] Mockito 사용법 (4) - 검증 (Verify) (0) | 2022.09.06 |
[Java] Mockito 사용법 (2) - 설정, Mock 생성 (@Mock, @Spy, @InjectMocks) (0) | 2022.09.06 |
[Java] Mockito 사용법 (1) - Mock이란?, Mockito 소개 (1) | 2022.09.06 |
[Java] Apache Log4j 2.x 취약점 및 해결 방법 (Log4j2 remote code execution vulnerability) (8) | 2021.12.12 |
댓글