TIL

10장 마이크로서비스 테스트 2부

10.1 통합 테스트 작성

상호 작용 유형 컨슈머 프로바이더 계약
REST 요청/응답 API 게이트웨이 주문 서비스 http 요청/응답
발행/구독 주문 이력 서비스 주문 서비스 도메인 이벤트
비동기 요청/응답 주문 서비스 주방 서비스 커맨드 메시지 및 응답 메시지

10.1.1 통합 테스트: 영속화

10.1.2 통합 테스트: REST 요청/응답형 상호 작용

REST API 계약 예제

org.springframework.cloud.contract.spec.Contract.make {
  request {
    method 'GET'
    url '/orders/1223232'
  }
  response {
    status 200
    headers {
      header('Content-type': 'application/json;charset=UTF-8')
    }
    body(```{"orderId" : "1223232", "state" : "APPROVAL_PENDING"}```)
  }
}

컨슈머 주도 계약 통합 테스트: 주문 서비스

public abstract class HttpBase {
  private StandaloneMockMvcBuilder controllers(Object ... controllers) {
    ...
    return MockMvcBuilders.standaloneSetUp(controllers)
      .setMessageConverters(...);
  }
  
  @Before
  public void setup() {
    OrderService orderService = mock(OrderService.class);
    orderRepository orderRepository = mock(OrderRepository.class);
    OrderController orderController = new OrderController(orderSercice, orderRepository);
    
    when(orderRepository.findById(OrderDetailsMother.ORDER_ID))
      .thenReturn(Optional.of(OrderDetailsMother.CHICKEN_VINDALOO_ORDER));
    ...
  }
}

소비자 쪽 통합 테스트: API 게이트웨이의 OrderServiceProxy

@RunWith(SpringRunner.class)
@SpringBootTest(classes=TestConfiguration.class, 
  webEnvironment=SpringBootTest.WebEnvironment.NONE)
@AutoConfigureStupRunner(ids = // 스프링 클라우드 컨트랙트가 주문 서비스 계약대로 와이어목을 구성
  {"net.chrisrichardson.ftgo.contract:ftgo-order-service-contracts"}
)
@DirtiesContext
public class OrderServiceProxyIntegrationTest {
  
  @Value("${stubrunner.runningstubs.ftgo-order-service-contracts.port}") // 와이어목이 실행 중인 랜덤 포트 획득
  private int port;
  private OrderDestinations orderDestinations;
  private OrderServiceProxy orderService;
  
  @Before
  public void setUp() throws Exception {
    orderDestinations = new OrderDestinations();
    orderDestinations.setOrderServiceUrl("http://localhost:" + port);
    orderService = new OrderServiceProxy(orderDestinations, WebClient.create());
  }
  
  @Test
  public void shouldVerifyExistingCustomer() {
    OrderInfo result = orderService.findOrderById("1223232").block();
    assertEquals("1223232", result.getOrderId());
    assertEquals("APPROVAL_PENDING", result.getState());
  }
  
  ...
}

10.1.3 통합 테스트: 발행/구독 스타일 상호 작용

OrderCreated 이벤트 발행 계약

package contracts;

org.springframework.cloud.contract.spec.Contract.make {
  label 'orderCreatedEvent' // 이벤트 발행을 트리거하는 컨슈머 테스트에서 사용
  input {
    triggeredBy('orderCreated()') // 코드-생성된 프로바이더 테스트에 의해 호출
  }
  
  outputMessage { // OrderCreated 도메인 이벤트
    sendTo('net.chrisrichardson.ftgo.orderservice.domain.Order')
    body(```{"orderDetails":{"lineItems":[{"quantity":5, "menuItemId":"1", ...}]}}```)
      headers {
        header('event-aggregate-type', 
          'net.chrisrichardson.ftgo.orderservice.domain.Order')
        ...
      }
  }
}

컨슈머 주도 계약 테스트: 주문 서비스

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MessagingBase.TestConfiguration.class,
  webEnvironment = SpringBootTest.WebEnvironment.NONE)
@AutoConfigureMessageVerifier
public abstract class MessagingBase {

  @Configuration
  @EnableAutoConfiguration
  @Import({
    EventuateContractVerifierConfiguration.class
    TramEventsPublisherConfiguration.class,
    TramInMemoryConfiguration.class})
  public static class TestConfiguration {
    
    @Bean
    public OrderDomainEventPublisher orderAggregateEventPublisher(
                        DomainEventPublisher eventPublisher) {
      return new OrderDomainEventPublisher(eventPublisher);
    }
  }
  
  @Autoworied
  private OrderDomainEventPublisher orderAggregateEventPublisher;
  
  protected void orderCreated() { // 코드-생성된 테스트 하위 클래스는 orderCreated()를 호출하여 이벤트를 발행
    orderAggregateEventPublisher.publish(CHCKEN_VINDALOO_ORDER, 
      Collections.singletonList(new OrderCreatedEvent(...)));
  }
}

소비자 쪽 계약 테스트: 주문 이력 서비스

@RunWith(SpringRunner.class)
@SpringBootTest(classes = OrderHistoryEventHandlerTest.TestConfiguration.class, 
  webEnvironment=SpringBootTest.WebEnvironment.NONE)
@AutoConfigureStubRunner(ids = 
  {"net.chrisrichadson.ftgo.contracts:ftgo-order-service-contracts"}
)
@DirtiesContext
public class OrderHistoryHandlersTest {

  @Configuration
  @EnableAutoConfiguration
  @Import({
    OrderHistoryServiceMessagingConfiguration.class, 
    TramCommandProducerConfiguration.class,
    TramInMemoryConfiguration.class,
    EventuateContractVerifierConfiguration.class
  })
  public static class TestConfiguration {
    
    @Bean
    public ChannelMapping channelMapping() {
      return new DefaultChannelMapping.DefaultChannelMappingBuilder().build();
    }
    
    @Bean
    public OrderHistoryDao orderHistoryDao() {
      return mock(OrderHistoryDao.class);
    }
  }
  
  @Test
  public void shouldHandleOrderCreatedEvent() throws ... {
    ...
    stubFinder.trigger("orderCreatedEvent"); // orderCreatedEvent 스텁을 트리거하여 이벤트 발행
    
    eventually(() -> // 이벤트를 잘 소비했는지 검증
      ...
    )
  }
}

10.1.4 통합 계약 테스트: 비동기 요청/응답 상호 작용