[Spock] (Set 같은) Collection 전달인자 모킹(mocking)하기

by 스뎅(thDeng) on

문제: 다음 테스트는 통과할까??

def "으음 무슨 테스트지"() {
    given:
    Order order = new Order(id: 123L, orderItems: [
            new OrderItem(id: 1L, productId: 11L),
            new OrderItem(id: 2L, productId: 22L),
    ])

    orderRepository.findById(order.id) >> order
    productService.findProductNames(order.orderItems*.productId as Set) >> ["이거", "저거"]

    when:
    List<String> actual = orderService.fetchOrderItemNames(order.id)

    then:
    actual == ["이거", "저거"]
}

테스트하는 클래스는 아래처럼 생겼다. 다들 알만한 필드나 구현은 생략하고 테스트할 코드만 남겼다.

class OrderService {

    /* fields */

    public List<String> fetchOrderItemNames(Long orderId) {
        Order order = orderRepository.findById(orderId);
        Set<Long> productIds = order.getOrderItems().stream()
                .map({it.productId})
                .map(OrderItem::getProductId)
                .collect(Collectors.toSet());
        return productService.findProductNames(productIds);
    }
}

class ProductService {
    public List<Long> findProductNames(Collection<Long> productIds) {
        /* fetch and return names */
    }
}

테스트가 통과하는지 물어보는거 보니 테스트는 실패할거라는걸 짐작할 수 있다. Set<Long> productIds = order.getOrderItems().stream() 부분의 SetList로 바꾸기만 해도 테스트는 통과한다. 그런데 왜 Set만 안 되는걸까??

원인은 아래처럼 모킹을 하면 productService.findProductNames이 모킹되지 않는다는 것이다. 이런저런 형태로 캐스팅을 해주고 이리저리 바꿔줘도 계속 모킹이 되지 않고 null만 반환한다.

// 이렇게 ID를 직접 주고 Set으로 캐스팅을 해도 모킹이 원하는 대로 되지 않고, null이 반환된다. 피곤쓰
productService.findProductNames(order.orderItems*.productId as Set) >> ["이거", "저거"]
productService.findProductNames([11L, 22L] as Set) >> ["이거", "저거"]
productService.findProductNames([11L, 22L] as Collection) >> ["이거", "저거"]
productService.findProductNames((Set) [11L, 22L]) >> ["이거", "저거"]

좀 설명하기 묘한데, groovy는 as 키워드로 캐스팅을 하게 된다. 그런데 위처럼 캐스팅 해서 모킹을 하는 경우, 실제로 모킹되는 값은 원래 형태인 [11L, 22L]과 같은 List인 것이다. 때문에 아래처럼 실제 그 타입 객체를 만들어 내서 테스트를 하면 통과하는걸 볼 수 있다. (헣허 )

Collection<Long> productIds = order.orderItems*.productId as Set
productService.findProductNames(productIds) >> ["이거", "저거"]

// 또는

productService.findProductNames(order.orderItems*.productId.toSet()) >> ["이거", "저거"]

// 또는

productService.findProductNames([11L, 22L].toSet()) >> ["이거", "저거"]

Set으로 캐스팅하고 변수로 받아서 넣어주거나, .toSet()을 불러서 Set으로 바꿔서 모킹을 하면 된다. 아래 테스트는 잘 동작한다.

def "으음 무슨 테스트지"() {
    given:
    Order order = new Order(id: 123L, orderItems: [
            new OrderItem(id: 1L, productId: 11L),
            new OrderItem(id: 2L, productId: 22L),
    ])

    orderRepository.findById(order.id) >> order
    productService.findProductNames(order.orderItems*.productId.toSet()) >> ["이거", "저거"]

    when:
    List<String> actual = orderService.fetchOrderItemNames(order.id)

    then:
    actual == ["이거", "저거"]
}
별도로 명시하지 않을 경우, 이 블로그의 포스트는 다음 라이선스에 따라 사용할 수 있습니다: Creative Commons License CC Attribution-NonCommercial-ShareAlike 4.0 International License