package org.prgms.kdt.order;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.prgms.kdt.voucher.FixedAmountVoucher;
import org.prgms.kdt.voucher.MemoryVoucherRepository;
import org.prgms.kdt.voucher.VoucherService;
import java.util.List;
import java.util.UUID;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.*;
class OrderServiceTest {
// stub을 만든다면 아래와 같이 만들 수 있습니다.
class OrderRepositoryStub implements OrderRepository {
@Override
public Order insert(Order order) {
return null;
}
}
@Test
@DisplayName("오더가 생성되야한다. (stub)")
void createOrder() {
// Given
var voucherRepository = new MemoryVoucherRepository();
var fixedAmountVoucher = new FixedAmountVoucher(UUID.randomUUID(), 100);
voucherRepository.insert(fixedAmountVoucher);
var sut = new OrderService(new VoucherService(voucherRepository), new MemoryOrderRepository());
// When
var order = sut.createOrder(UUID.randomUUID(), List.of(new OrderItem(UUID.randomUUID(), 200, 1)), fixedAmountVoucher.getVoucherId());
// Then
assertThat(order.totalAmount(), is(100L));
assertThat(order.getVoucher().isEmpty(), is(false));
assertThat(order.getVoucher().get().getVoucherId(), is(fixedAmountVoucher.getVoucherId()));
assertThat(order.getOrderStatus(), is(OrderStatus.ACCEPTED));
}
@Test
@DisplayName("오더가 생성되야한다. (mock)")
void createOrderByMock() {
// Given
var voucherServiceMock = mock(VoucherService.class);
var orderRepositoryMock = mock(OrderRepository.class);
var fixedAmountVoucher = new FixedAmountVoucher(UUID.randomUUID(), 100);
when(voucherServiceMock.getVoucher(fixedAmountVoucher.getVoucherId())).thenReturn(fixedAmountVoucher);
var sut = new OrderService(voucherServiceMock, orderRepositoryMock);
// When
var order = sut.createOrder(
UUID.randomUUID(),
List.of(new OrderItem(UUID.randomUUID(), 200, 1)),
fixedAmountVoucher.getVoucherId());
// Then
// 오더에 대한 상태 검증
assertThat(order.totalAmount(), is(100L));
assertThat(order.getVoucher().isEmpty(), is(false));
// 바우처 서비스가 어 행동을 하는지 어떤 메소드가 호출 되는지 검증
// inOrder를 사용하여 실제 각각의 목들이 어떠한 순서대로 호출이 되는지 까지 검증이 가능합니다.
var inOrder = inOrder(voucherServiceMock, orderRepositoryMock);
inOrder.verify(voucherServiceMock).getVoucher(fixedAmountVoucher.getVoucherId());
inOrder.verify(orderRepositoryMock).insert(order);
inOrder.verify(voucherServiceMock).useVoucher(fixedAmountVoucher);
}
}
Testing
스프링 공식문서에서 스프링에서 테스팅 지원을 어떻게 하는지 설명되어있습니다.
Constructor Based Dependency Injection Pattern(생성자 기반 의존관계 주입)으로 작성한 코드와 같이 컨테이너에서 Autowired를 이용하여 만들필요 없이 Mock객체를 만들어 주입함으로써 테스트를 할 수 있습니다. 따라서 컨테이너 없이 단위 테스트 코드를 작성할 수 있습니다.
실제 스프링 테스트에서 가장 많이 서포트해주는 부분입니다. 실제 서버를 띄우지 않고 스프링 컨테이너 내에서 각각의 등록된 빈들 간의 연동되어지는 부분들에 테스트가 필요할 때가 많습니다. 이 테스트를 하기 위한 스프링 컨테이너가 필요하며 스프링은 테스트시 IoC 컨테이너를 제공하기 위해서 Spring TestContext Framework를 제공합니다. 테스트 환경에서 ApplicationContext를 애노테이션만으로 쉽게 만들 수 있습니다.
package org.prgms.kdt;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.prgms.kdt.order.OrderItem;
import org.prgms.kdt.order.OrderService;
import org.prgms.kdt.order.OrderStatus;
import org.prgms.kdt.voucher.FixedAmountVoucher;
import org.prgms.kdt.voucher.VoucherRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import java.util.List;
import java.util.UUID;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
//@ExtendWith(SpringExtension.class)
// ContextConfiguration 은 어떤식으로 applicationContext 가 만들어져야 되는지만 알려줍니다.
// 실질적으로 JUnit 과 상호작용해서 testContext 를 만드는 것은 SpringExtension 을 이용해야합니다.
//@ContextConfiguration
// ExntendsWith, ContextConfiguration을 포함하는 애노테이션
@SpringJUnitConfig
@ActiveProfiles("test")
public class KdtSpringContextTests {
// ContextConfiguration에 별도로 빈설정을 정의한 클래스나 xml을 전달하지 않으면
// 현재 클래스내에 Configuration 애노테이션이 달린 클래스를 찾게됩니다.
@Configuration
@ComponentScan(
basePackages = {"org.prgms.kdt.voucher", "org.prgms.kdt.order"}
)
static class Config {
}
@Autowired
ApplicationContext context;
@Autowired
OrderService orderService;
@Autowired
VoucherRepository voucherRepository;
@Test
@DisplayName("applicationContext가 생성 되야한다.")
public void testApplicationContext() {
assertThat(context, notNullValue());
}
@Test
@DisplayName("VoucherRepository가 빈으로 등록되어 있어야 한다.")
public void testVoucherRepositoryCreation() {
var bean = context.getBean(VoucherRepository.class);
assertThat(bean, notNullValue());
}
@Test
@DisplayName("orderService를 사용해서 주문을 생성할 수 있다.")
public void testOrderService() {
// Given
var fixedAmountVoucher = new FixedAmountVoucher(UUID.randomUUID(), 100);
voucherRepository.insert(fixedAmountVoucher);
// When
var order = orderService.createOrder(
UUID.randomUUID(),
List.of(new OrderItem(UUID.randomUUID(), 200, 1)),
fixedAmountVoucher.getVoucherId());
// Then
assertThat(order.totalAmount(), is(100L));
assertThat(order.getVoucher().isEmpty(), is(false));
assertThat(order.getVoucher().get().getVoucherId(), is(fixedAmountVoucher.getVoucherId()));
assertThat(order.getOrderStatus(), is(OrderStatus.ACCEPTED));
}
}