Spring Framework.

Mockito를 이용한 Service Layer 테스트

PI.314 2022. 2. 6. 04:52

1.  비즈니스 로직 설명

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
     OrderRepository orderRepository;

    @Autowired
    public OrderServiceImpl(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    @Override
    public OrderDto createOrder(OrderDto orderDto) {
        ModelMapper mapper = new ModelMapper();
        mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
        OrderEntity orderEntity = mapper.map(orderDto, OrderEntity.class);
        return mapper.map(orderRepository.save(orderEntity), OrderDto.class);
    }

OrderService의 createOrder 함수와 연관된 비즈니스 로직 테스트를 진행하고자 한다.

간단하게 살펴보면 OrderRepository에 의존하여 DB에 Order 정보를 저장하는 로직이다.

createOrder 메소드를 보면 orderRepository.save(orderEntity) 를 통해 Repository에 데이터를 저장하고 Entity를 반환해오는 것을 알 수 있다.

 

public interface OrderRepository extends CrudRepository<OrderEntity, Long> {
    OrderEntity findByOrderId(String orderId);
    Iterable<OrderEntity> findByUserId(String userId);
}

JPA를 사용하지않는 분들을 위해 참고로 설명드리면, OrderRepository는 기본적인 CRUD기능을 제공하는 CrudRepository 인터페이스를 상속받고 있기 때문에 save함수를 따로 작성하지 않아도 된다.

 

CurdRepository

 

2.  테스트 관련 설명

public class TestInfoFixture {
    public static final String ORDER_ID = "orderId";
    public static final String USER_ID = "userId";
    public static final String PRODUCT_ID = "productId";
    public static final int QTY = 10;
    public static final int UNIT_PRICE = 10000;
}

TestInfoFixture 클래스는 테스트를 유지보수를 용이하게 하기 위해서 만든 테스트 픽스쳐 클래스다.

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock
    private OrderRepository orderRepository;

    @InjectMocks
    private OrderServiceImpl orderService;

    private OrderDto orderDto;
    private OrderEntity orderEntity;

    @BeforeEach
    void setUp() {
        orderDto = OrderDto.builder()
                .orderId(ORDER_ID)
                .productId(PRODUCT_ID)
                .qty(QTY)
                .unitPrice(UNIT_PRICE)
                .totalPrice(QTY * UNIT_PRICE)
                .build();

        ModelMapper mapper = new ModelMapper();
        mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
        orderEntity = mapper.map(orderDto, OrderEntity.class);
    }

    @Test
    @DisplayName("주문정보 생성 성공")
    void createOrder() {
        // given
        given(orderRepository.save(any())).willReturn(orderEntity);

        // when
        final OrderDto result = orderService.createOrder(orderDto);

        // then
        verify(orderRepository).save(any());
        assertThat(result.getOrderId()).isNotEmpty();
        assertThat(result.getTotalPrice()).isEqualTo(result.getUnitPrice() * result.getQty());
    }

Service 레이어의 단위 테스트를 진행할 때는, 모든 빈이 등록될 필요도 없고 실제로 스프링 컨테이너에 올라갈 필요도 없다.

때문에 @SpringBootTest 대신에 @ExtendWith(MockitoExtensions.class)를 사용하면 Spring에 의존하지 않고 테스트 범위에 해당하는 OrderService 객체만 실제로 생성되어 빠른 테스트를 제공할 수 있다.

 

mock객체를 통해 OrderService에 해당하는 테스트 범위에 맞게 최소한의 자원만 사용하면 된다.

// given
given(orderRepository.save(any())).willReturn(orderEntity);

먼저 @Mock을 통해 OrderRepository의 mock객체를 생성해준다. 그리고 given 영역에서  orderRepository.save함수가 호출되면 미리 생성해둔 orderEntity를 반환할 수 있도록 스터빙한다.

@InjectMocks
private OrderServiceImpl orderService;
// when
final OrderDto result = orderService.createOrder(orderDto);

그리고 when 영역에서 orderService의 createOrder함수를 실제로 호출해줘야하는데 orderService에 orderRepository가 의존해 있기 때문에, 가짜 의존성을 주입해주기 위해서 @InjectMocks을 사용한다.

// then
verify(orderRepository).save(any());
assertThat(result.getOrderId()).isNotEmpty();
assertThat(result.getTotalPrice()).isEqualTo(result.getUnitPrice() * result.getQty());

 

마지막으로 then영역에서 orderRepository.save 함수가 동작하는지 검증하고 assertThat을 통해 기대값과 결과값을 비교한다.