Spring(6) - Mock, Mockito
이번 포스팅은 Short Url API 프로젝트 도중 CI를 이용하려고 테스트 코드를 짜던 중
DB에 접근하고 설정해줘야하는것이 까다로웠습니다.
그래서 이러한 데이터들을 코드에서 바로 지정해 쉽게 테스트를 구성 할 수 없을까? 라는 생각이 들었습니다.
그 때 눈에 들어온 방법이 "Mockito"입니다. Mockito는 Mock을 Java Mock객체를 이용한 테스트를 편하게 해주는
라이브러리입니다. 그럼 자세히 살펴보겠습니다.
Mock이란?
Mock은 Object입니다.
테스트를 수행 할 경우 "가짜"모듈을 생성하여 테스트의 효율성을 높이는 데사용되는 객체입니다.
그럼 왜 이런 걸 사용할까요?
예를 들어 DB에서 멤버를 꺼내오는 작업이 있다고 합시다. 근데 여기서 DB에서 조회기능을 수행,
DB에 있는 값만 사용가능이라는 제약들이 걸려있습니다.
Mock객체를 사용하면 이러한 제약없이사용이 가능합니다. 어떻게 Mock을 사용할까요?
바로 Mockito라는 프레임워크를 사용합니다.
이 프레임워크는 mock의 행동을 지정하는 stubbing, 정상적으로 작동하는지 verify등 다양한 기능을 제공합니다.
Stubbing(스터빙)
Mock객체의 메소드를 실행하였을 때 어떤 값으로 리턴할지를 정의해놓은 것.
- 스터빙 방법
1. OngoingStubbing메소드
<Product.java>
public class Product {
private String name;
private Integer price;
public Product(String name, Integer price) {
this.name = name;
this.price = price;
}
}
<ProductService>
public class ProductService {
public Product getProduct() {
return new Product("monitor",15000);
}
public Product getProduct(String name, Integer price) {
return new Product(name,price);
}
}
<MockTest.java>
@ExtendWith(MockitoExtension.class)
public class MockTest {
@Mock
ProductService productService;
@Test
void mockTest1() {
Product product = new Product("desk",15000);
when(productService.getProduct()).thenReturn(product);
Assertions.assertThat(productService.getProduct()).isEqualTo(product);
}
}
다음과 같이 생성한 후 when을 이용하여 메소드의 리턴값을 정해줬습니다.
이것 이외에도 then을 따로 지정해줄 수 있습니다.
thenReturn | 스터빙한 메소드 호출 후 어떤 객체를 리턴할 건지 정의 |
thenThrow | 스터빙한 메소드 호출 후 어떤 예외를 발생시킬건지 정의 |
thenAnswer | 스터빙한 메소드 호출 후 어떤 작업을 할지 custom하게 정의 |
thenCallRealMethod | 실제 메소드 호출 |
@Test
void mockThenThrowTest() {
when(productService.getProduct()).thenThrow(new IllegalAccessError());
assertThatThrownBy(() -> productService.getProduct()).isInstanceOf(IllegalAccessError.class);
}
@Test
void mockThenAnswerTest() {
Product product = new Product("desk",15000);
when(productService.getProduct()).thenAnswer(invocation -> {
System.out.println("MockTest.mockThenAnswerTest");
return product;
}
);
assertThat(productService.getProduct()).isEqualTo(product);
}
위의 예시는 thenAnswer을 실행한다. 이것은 함수가 리턴되기 전 실행해야 될 함수를 지정해줍니다.
그래서 위와 같이 짜면 출력 후 product를 리턴해줍니다.
2. Stubber메소드
when(...) thenReturn(...)
mock되기 전에 실제 메소드를 호출하고, 결과값이 기대값을 반환해야한다고 지정한다.
doReturn(...) when(...)
실제 메소드를 호출하지도 않고, 결과값도 기대값이 반환해야 한다고 지정한다.
doReturn | 스터빙한 메소드 호출 후 어떤 객체를 리턴할 건지 정의 |
doThrow | 스터빙한 메소드 호출 후 어떤 예외를 발생시킬건지 정의 |
doAnswer | 스터빙한 메소드 호출 후 어떤 작업을 할지 custom하게 정의 |
doCallRealMethod | 실제 메소드 호출 |
doNothing | 스터빙 메소드 호출 후 어떤 행동도 하지 않게 정의 |
Mock의 생성
일단 Mockito의존성을 추가해줍니다.
testImplementation 'org.mockito:mockito-core:3.12.2'
사용하는 Test클래스에 @ExtendsWith(MockitoExtensions.class)를 붙여줍니다.
JUNIT4는 @RunWith()를 붙여주면 됩니다.
그리고 Mock을 생성해줍니다. 방법은 3가지 존재합니다.
1. @Mock
가짜 객체이며 그 안의 메소드 호출 시 반드시 Stubbing을 해야합니다.
Stubbing하지 않고 호출하신다면 값 타입은 0, 참조형은 null을 반환합니다.
@ExtendWith(MockitoExtension.class)
public class MockCreateTest {
@Mock
ProductService productService;
@Test
void createMockNoWithStubbing() {
Assertions.assertNull(productService.getProduct());
}
@Test
void createMockWithStubbing() {
Product product = new Product("desk",15000);
when(productService.getProduct()).thenReturn(product);
org.assertj.core.api.Assertions.assertThat(productService.getProduct()).isEqualTo(product);
}
}
2. @Spy
진짜 객체이며 스터빙하지 않으면 진짜 메소드, 스터빙하면 스터빙 값을 리턴합니다.
@ExtendWith(MockitoExtension.class)
public class SpyCreateTest {
@Spy
ProductService productService;
@Test
void createSpyNoWithStubbing() {
Product product = productService.getProduct();
Assertions.assertThat(productService.getProduct().getName()).isEqualTo(product.getName());
}
@Test
void createSpyWithStubbing() {
Product product = new Product("chair",20000);
when(productService.getProduct()).thenReturn(product);
Assertions.assertThat(productService.getProduct()).isNotEqualTo(product);
}
}
3. @InjectionMock
이것은 위의 @Mock또는 @Spy를 의존성 주입을 받아서 사용할 수 있는 것입니다.
Verify(검증)
검증 부분은 mock객체에 대해서 자세한 세부 내용을 확인할 수 있습니다.
times(n) | 몇 번이 호출됐는지 검증 |
never | 한 번도 호출되지 않았는지 검증 |
atLeastOne | 최소 한 번은 호출됐는지 검증 |
atLeast(n) | 최소 n 번이 호출됐는지 검증 |
atMostOnce | 최대 한 번이 호출됐는지 검증 |
atMost(n) | 최대 n 번이 호출됐는지 검증 |
calls(n) | n번이 호출됐는지 검증 (InOrder랑 같이 사용해야 함) |
only | 해당 검증 메소드만 실행됐는지 검증 |
이렇게 Mock에 대해서 직접 구현해보고 어떻게 작동하는지 알아봤습니다.
나중에 DB가 복잡하게 연결되어있는 경우에 확실히 테스트 시간을 줄일 수 있는 방법입니다.
Mock을 프로젝트에 적용시켜보면서 더 능숙하게 활용 할 수 있을 것 같습니다.