TEST/JUnit

단위 테스트 방법 [part 2 : 서비스(Service)]

블로그 주인장 2023. 11. 8.

적합한 테스트 가이드

레이어(layer)별로 사용하기 적합한 방식의 테스트 가이드에 대해 알아보겠습니다.

그 중에 '서비스 객체'를 테스트하는 방법을 알아보겠습니다.


1️⃣ ProductService 메서드 테스트

class ProductServiceTest {

    private final ProductRepository productRepository
            = Mockito.mock(ProductRepository.class);
            
    private ProductServiceImpl productService;

    @BeforeEach
    public void setUpTest() {
        productService = new ProductServiceImpl(productRepository);
    }

    @Test
    void getProductTest() {
        //given
        Product product = Product.builder()
                .number(123L)
                .name("pen")
                .price(1000)
                .stock(1234)
                .build();

        Mockito.when(productRepository.findById(123L))
                .thenReturn(Optional.of(product));

        //when
        ProductResponseDto responseDto
                = this.productService.getProduct(123L);

        //then
        Assertions.assertEquals(responseDto.getNumber(), product.getNumber());
        Assertions.assertEquals(responseDto.getName(), product.getName());
        Assertions.assertEquals(responseDto.getPrice(), product.getPrice());
        Assertions.assertEquals(responseDto.getStock(), product.getStock());

        verify(productRepository).findById(123L);
    }
}
  • 단위 테스트를 위해서는 외부 요인을 모두 배제하도록 작성해야한다.(@SpringBootTest, @WebMvcTest 등 선언 x)
  • Mockito.mock() 메서드를 통해 Mock 객체로 ProductRepository를 주입받았다.
  • 해당 객체를 기반으로 테스트 전에 ProductService 객체를 초기화하여 사용한다.
  • 해당 테스트 코드는 Given-When-Then 패턴을 기반으로 작성되었다.

Given-When-Then 패턴

  • Given 구문 : 테스트 사용될 Product 엔티티 객체 생성 및 ProductRepository의 동작에 대한 리턴 값을 설정한다.
  • When 구문 : 테스트하고자 하는 ProductService의 getProduct() 메서드를 호출해서 동작을 테스트한다.
  • Then 구문 : 테스트에서 리턴받은 responseDto의 값을 Assertion 을 통해 값을 검증함으로 테스트의 목적을 확인하고, verify() 메서드로 검증보완 및 부가 검증을 시도한다.

 

2️⃣ ProductService 메서드 테스트

class ProductServiceTest {

    private final ProductRepository productRepository
            = Mockito.mock(ProductRepository.class);
            
    private ProductServiceImpl productService;

    @BeforeEach
    public void setUpTest() {
        productService = new ProductServiceImpl(productRepository);
    }

   @Test
    void saveProductTest() {
        //given
        Mockito.when(productRepository.save(any(Product.class)))
                .then(returnsFirstArg());

        //when
        ProductResponseDto responseDto
                = productService.saveProduct(new ProductDto("pen", 1000, 1234));

        //then
        Assertions.assertEquals(responseDto.getName(), "pen");
        Assertions.assertEquals(responseDto.getPrice(), 1000);
        Assertions.assertEquals(responseDto.getStock(), 1234);

        verify(productRepository).save(any());
    }
}
  • any()
    1. Mockito의 ArgumentMatchers에서 제공하는 메서드이다.
    2. Mock 객체의 동작을 정의하거나 검증하는 단계의 조건이다.
    3. 특정 매개변수의 전달을 설정하지 않고 메서드의 실행만을 확인하거나 좀 더 큰 범위의 클래스 객체를 매개변수로 전달받는 등의 상황에 사용한다.
  • ex) any(Product.class)
    • 일반적으로 given()으로 정의된 Mock 객체의 메서드 동작 감지는 매개변수의 비교를 통해 이루어진다.
    • 레퍼런스 변수의 비교는 주소값으로 이뤄지기 때문에 any()를 사용해 클래스만 정의하는 경우도 있다.

 

3️⃣ ProductService 메서드 테스트(@MockBean 어노테이션 이용)

@ExtendWith(SpringExtension.class)
@Import({ProductServiceImpl.class})
class productServiceTest2 {

    @MockBean
    ProductRepository productRepository;

    @Autowired
    ProductService productService;
    
    //...이하 생략...
}
  • @MockBean을 사용하는 방식
    • 스프링에 Mock 객체를 등록해서 주입받는 형식이다.
    • 스프링을 사용하지 않는 Mock 객체를 직접 생성하는 방식이 더 빠르게 동작한다.
  • @ExtendWith(SpringExtension.class) 
    • JUnit5 테스트에서 스프링 테스트 컨텍스트를 사용하도록 설정한다.
  • @Import 
    • @AutoWired 어노테이션을 주입받는 ProductService를 주입받기 위해 사용한다. 
반응형

댓글