Back-end/Spring

Spring(6) - Mock, Mockito

HOONY_612 2021. 8. 25. 17:02
반응형

이번 포스팅은 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을 프로젝트에 적용시켜보면서 더 능숙하게 활용 할 수 있을 것 같습니다.

 

 

 

반응형