본문 바로가기
Develop/Spring˙Spring Boot

[Spring Reactive] 리액티브 테스트 작성하기

by 독서왕뼝아리 2023. 3. 13.

스프링부트와 리액터에서는 비동기, 논블로킹 코드도 JUnit을 이용해 테스트할 수 있다.

 

리액티브 단위 테스트

리액티브 코드를 테스트할 때 핵심은 기능만을 검사하는 게 아니라 리액티브 스트림 시그널도 함께 검사해야 한다는 점이다. 리액티브 스트림은 onSubscribe, onNext, onError, onComplete를 말한다.

 

@ExtendWith(SpringExtension.class)
class InventoryServiceTest {

    InventoryService inventoryService;
    @MockBean
    private ItemRepository itemRepository;
    @MockBean
    private CartRepository cartRepository;

    @BeforeEach
    void setUp() {
        Item sampleItem = new Item("item1", "TV tray", "Alf TV tray", 19.99);
        CartItem sampleCartItem = new CartItem(sampleItem);
        Cart sampleCart = new Cart("My Cart", Collections.singletonList(sampleCartItem));

        when(cartRepository.findById(anyString())).thenReturn(Mono.empty());
        when(itemRepository.findById(anyString())).thenReturn(Mono.just(sampleItem));
        when(cartRepository.save(any(Cart.class))).thenReturn(Mono.just(sampleCart));

        inventoryService = new InventoryService(itemRepository, cartRepository);
    }

}

기본적인 테스트 셋업은 위와 같이 한다. 단위 테스트를 수행할 것이므로 MockBean을 주입하여 테스트하자.

 

 

@Test
void addItemToEmptyCartShouldProduceOneCartItem(){
    inventoryService.addItemToCart("My Cart", "item1")
            .as(StepVerifier::create)
            .expectNextMatches(cart -> {
                assertEquals(cart.getCartItems().stream().map(CartItem::getQuantity), 1);

                assertEquals(cart.getCartItems().stream().map(CartItem::getItem),
                        new Item("item1","TV tray","Alf TV tray", 19.99));

                return true;
            });
}

테스트 코드도 누군가가 구독을 해야 무슨 일이 발생하고 검증도 할 수 있게 된다. StepVerifier라는 클래스?가 구독을 한다. 결괏값을 얻기 위해 블로킹 방식으로 기다리는 대신에  리액터의 테스트 도구가 대신 구독을 하고 값을 확인할 수 있게 해준다. 값을 검증할 수 있는 적절한 함수를 expectNextMatches에 람다식 인자로 전달해주고, verifyComplete()를 호출해 onComplete 시그널을 확인하면 의도한 대로 테스트가 동작했음이 보장된다.

 

 


내장 컨테이너 테스트 실행

종단 간 테스트는 비용이 많이 들지만 넓은 범위의 테스트를 할 필요가 있다. 스프링 부트의 강력한 기능인 내장 웹 컨테이너를 사용해 어렵지 않게 테스트 코드를 구현할 수 있다.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // 1
@AutoConfigureWebTestClient // 2
public class LoadingWebSiteIntegrationTest {
    @Autowired
    WebTestClient client; 

    @Test
    void test() { // 3
        client.get().uri("/").exchange()
                .expectStatus().isOk()
                .expectHeader().contentType(MediaType.TEXT_HTML)
                .expectBody(String.class)
                .consumeWith(exchangeResult -> {
                    assertThat(exchangeResult.getResponseBody()).contains("<a href=\"/add");
                });
    }
}
  1. 스프링 부트가 실제 애플리케이션을 구동하게 만든다. @SpringBootApplication이 붙은 클래스를 찾아서 내장 컨테이너를 실행한다. WebEnvironment.RANDOM_PORT는 테스트할 때 임의의 포트에 내장 컨테이너를 바인딩한다.
  2. 애플리케이션에 요청을 날리는 WebTestClient 인스턴스를 생성한다.
  3. 실제 테스트 메소드는 WebTestClient를 사용해서 홈 컨트롤러의 루트 경로를 호출한다.