반응형
Mockito
Mockito 란 ?
- 복잡하게 얽혀있는, 의존성을 가지는 객체들을 가짜 객체로 만들어 테스트할 수 있는데 이런 객체를 Mock 객체라고 한다. (참고)
- Mockito는 관리가 어려운 Mock 객체를 손쉽게 사용하도록 지원해주는 프레임워크다.
Mockito 단계
- Mock 객체 생성
- Mock 객체 행동 (Stubbing) : when 메서드를 통해 원하는 동작을 미리 정하고 이를 기반으로 테스트한다.
- Mock 작동 검증 (Verify) : 호출 등이 정확히 됐는지 확인한다.
Mock 생성 방식
- mock Method
import static org.mockito.Mockito.mock;
public class MockStudy {
private UserService userService;
@Test
public void test() {
userService = mock(UserService.class);
// ...
}
}
- @Mock annotation
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class MockStudy {
@Mock
private UserService userService;
@Test
public void test() {
// ...
}
}
Stubbing (Mock 객체 행동)
- when : Mock 객체의 행동을 조작하는 메소드
- mock을 사용하고, stubbing 해주지 않으면 reference 타입은 Null, Primtive 타입은 0을 반환함
Example
- UserService
public class UserService {
public User getUser() {
return new User("haenny", 28); // name, age
}
public int getLoginErrNum() {
return 1;
}
}
- Test Code @Mock
@ExtendWith(MockitoExtension.class)
public class MockStudy {
@Mock
UserService userService;
@Test
void testReferenceType() {
assertNull(userService.getUser()); // Test Success
}
@Test
void testPrimitiveType() {
assertEquals(0, userService.getLoginErrNum()); // Test Success
}
@Test
void testStubing() {
User user = new User("heng", 28);
when(userService.getUser()).thenReturn(user);
assertEquals("haenny", userService.getUser().getName()); // Test Fail
}
}
Stubbing(스터빙) 방법
1. OngoingStubbing method
when({stubbing method}).{ongoinStubbing method}
- when 메소드의 리턴 값을 정의해주는 메소드
thenReturn | 스터빙한 메소드 호출 후 어떤 객체를 리턴할 지 정의 |
thenThrow | 스터빙한 메소드 호출 후 어떤 Exception Throw할 지 정의 |
thenAnswer | 스터빙한 메소드 호출 후 어떤 작업을 할 지 커스텀 정의 - thenReturn, thenTrhow 사용 권장 |
thenCallRealMethod | 실제 메소드 호출 |
Example
- Service
public class ProductService {
public Product getProduct() {
return new Product("A001", "monitor");
}
public Product getProduct(String serial, String name) {
return new Product(serial, name);
}
}
- Test Code (OngoingStubbing method)
@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");
}
}
- 메소드 호출마다 바뀌는 스터빙 방법
: 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);
}
}
2. Stubber method
{stubber method}.when({스터빙할 클래스}).{스터빙할 메소드}
- 스터빙이 반드시 실행되어야 하는 경우 사용하는 메소드로, OngoingStubbing과 다르게 when에 스터빙할 클래스를 넣고 그 후에 메소드를 호출
doReturn | 스터빙 메소드 호출 후 어떤 행동을 할 지 정의 |
doThrow | 스터빙 메소드 호출 후 어떤 Exception을 throw할 지 정의 |
doAnswer | 스터빙 메소드 호출 후 어떤 작업을 할 지, 커스텀하게 정의 |
doNothing | 스터빙 메소드 호출 후 어떤 행동도 하지 않도록 정의 |
Example
// 예1
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);
// 예2
when(mock.foo()).thenThrow(new RuntimeException());
//불가능 : 이미 mock.foo()를 호출할 때 RuntimeException이 일어나기 때문에 bar를 리턴할 수 없음
when(mock.foo()).thenReturn("bar");
//위와 같은 경우를 doReturn을 사용한다.
doReturn("bar").when(mock).foo();
void method 테스트 가능
- when method 는 리턴 타입 인자로 T를 받기 때문에 void 메소드에 사용할 수 없었음
- Stubber method로 가능
public class UserService {
public User getUser() {
return new User("haenny", 28); // name, age
}
public int getLoginErrNum() {
return 1;
}
public void deleteUser() {}
}
// -------------------------------------------------------------------------
// Stubber Test Code
@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);
}
}
Verify (Mock 작동 검증)
- 스터빙한 메소드 검증 방법 (Mock 작동 확인)
- verify 메소드를 이용해 스터빙한 메소드의 실행여부/N번 실행여부/실행 초과 여부 등 다양하게 검증 가능
verify(T mock, VerificationMode mode) // VerificationMode : 검증할 값을 정의하는 메소드
Method
메소드명 | 설명 (테스트 내에서 ~) |
times(n) | 몇 번이 호출됐는지 검증 |
never | 한 번도 호출되지 않았는지 검증 |
atLeastOne | 최소 한 번은 호출됐는지 검증 |
atLeast(n) | 최소 n 번이 호출됐는지 검증 |
atMostOnce | 최대 한 번이 호출됐는지 검증 |
atMost(n) | 최대 n 번이 호출됐는지 검증 |
calls(n) | n번이 호출됐는지 검증 (InOrder랑 같이 사용해야 함) |
only | 해당 검증 메소드만 실행됐는지 검증 |
timeout(long mills) | n ms 이상 걸리면 Fail 그리고 바로 검증 종료 |
after(long mills) | n ms 이상 걸리는지 확인 timeout과 다르게 시간이 지나도 바로 검증 종료가 되지 않는다. |
description | 실패한 경우 나올 문구 |
Example (verify method)
@ExtendWith(MockitoExtension.class)
public class VerifyMethod {
@Mock
UserService userService;
@Test
void testVerifyTimes() {
userService.getUser();
userService.getUser();
verify(userService, times(2)).getUser();
}
@Test
void testVerifyNever() {
verify(userService, never()).getUser();
}
@Test
void testAtLeastOne() {
userService.getUser();
verify(userService, atLeastOnce()).getUser();
}
@Test
void testAtLeast() {
//한 번만 실행하면 fail
userService.getUser();
userService.getUser();
userService.getUser();
verify(userService, atLeast(2)).getUser();
}
@Test
void testAtMostOnce() {
userService.getUser();
// userService.getUser(); - 2번 이상 실행하면 fail
verify(userService, atMostOnce()).getUser();
}
@Test
void testAtMost() {
userService.getUser();
userService.getUser();
userService.getUser();
// userService.getUser(); - 6번 이상 실행하면 fail
verify(userService, atMost(3)).getUser();
}
@Test
void testOnly() {
userService.getUser();
// userService.getLoginErrNum(); - getUser()가 아닌 다른 메소드 실행 시 fail
verify(userService, only()).getUser();
}
}
Example (Method 호출 순서 검증)
- 메소드 호출 검증을 위해 InOrder 사용
- InOrder("Mock 객체 명")으로 생성 후 검증하고 싶은 순서에 맞게 verify 사용
- verifyNoMoreInteractions(T mock) : 선언한 verify 후 해당 mock 실행 시 fail
- verifyNoInteractions(T mock) : 테스트 내에서 mock 실행 시 fail
@ExtendWith(MockitoExtension.class)
public class VerifyInOrderMethod {
@Mock
UserService userService;
@Mock
ProductService productService;
@Test
void testInOrder() {
// 이 순서로 하면 fail
// userService.getLoginErrNum();
// userService.getUser();
userService.getUser();
userService.getLoginErrNum();
InOrder inOrder = inOrder(userService);
inOrder.verify(userService).getUser();
inOrder.verify(userService).getLoginErrNum();
}
@Test
void testInOrderWithCalls() {
// 이 순서로 하면 fail
// userService.getUser();
// userService.getLoginErrNum();
// userService.getUser();
userService.getUser();
userService.getUser();
userService.getLoginErrNum();
InOrder inOrder = inOrder(userService);
inOrder.verify(userService, calls(2)).getUser();
inOrder.verify(userService).getLoginErrNum();
}
@Test
void testInOrderWithVerifyNoMoreInteractions() {
userService.getUser();
// userService.getLoginErrNum(); - 실행하면 fail
InOrder inOrder = inOrder(userService);
inOrder.verify(userService).getUser();
verifyNoMoreInteractions(userService); //위에 verify 이후 userService를 호출하면 fail
}
@Test
void testInOrderWithVerifyNoInteractions() {
userService.getUser();
userService.getLoginErrNum();
// productService.getProduct(); - 실행하면 fail
InOrder inOrder = inOrder(userService);
inOrder.verify(userService).getUser();
inOrder.verify(userService).getLoginErrNum();
verifyNoInteractions(productService); //productService를 호출하면 fail
}
}
@Spy
- Spy로 만든 mock 객체는 진짜 객체이며, 스터빙을 하지 않으면 기존 객체의 로직을 실행한다
Example
- Service
public class UserService {
public User getUser() {
return new User("haenny", 28); // name, age
}
public int getLoginErrNum() {
return 1;
}
}
- Test @Spy
@ExtendWith(MockitoExtension.class)
public class MockAnnotation {
@Spy
UserService userService;
@Test
void testSpy_스터빙X() { // 스터빙 안한 객체는 실제 로직을 탄다
User user = userService.getUser();
assertEquals("haenny", user.getName()); // Test Success
}
@Test
void testSpy_스터빙O() {
User user = new User("heng", 29);
when(userService.getUser()).thenReturn(user); // 스터빙 했기 때문에, heng 반환
assertEquals("haenny", userService.getUser()); // Test Fail
}
}
@InjectMocks
- @Mock이나 @Spy로 생성된 Mock 객체를 자동으로 주입해줌
Example
- Service
public class OrderService {
UserService userService;
ProductService productService;
OrderService(UserService userService, ProductService productService) {
this.userService = userService;
this.productService = productService;
}
public User getUser() {
return userService.getUser();
}
public Product getProduct() {
return productService.getProduct();
}
}
- Test @InjectMocks
@ExtendWith(MockitoExtension.class)
public class InjectMocksAnnotation {
@Mock
UserService userService;
@Spy
ProductService productService;
@InjectMocks
OrderService orderService;
@Test
void testGetUser() {
assertNull(orderService.getUser());
}
}
- Test @InjectMocks 사용하지 않은 경우
@ExtendWith(MockitoExtension.class)
public class InjectMocksAnnotation {
@Mock
UserService userService;
@Spy
ProductService productService;
OrderService orderService;
@Test
void testGetUser() {
orderService = new OrderService(userService, productService);
assertNull(orderService.getUser());
}
}
728x90
반응형
'JVM > JUnit' 카테고리의 다른 글
[JUnit] 소개 - 6. MockMVC 란 ? (0) | 2023.09.30 |
---|---|
[JUnit] 소개 - 5. BDDMockito 란? (0) | 2023.09.27 |
[JUnit] 소개 - 3. Annotation (TestInstance, TestMethodOrder, Tag) (0) | 2023.09.21 |
[JUnit] 소개 - 2. Assertion 과 Assumption (0) | 2022.11.01 |
[JUnit] 소개 - 1. 테스트 종류와 JUnit 이란 (0) | 2022.11.01 |
댓글