๐Ÿ“Œ [MarketBridge] 05. ๊ฐœ๋ฐœ์ผ์ง€ - @SpringBootTest ์—†์ด Service ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ•˜๊ธฐ

๋ชจ๊น…ยท2024๋…„ 1์›” 31์ผ
0

๊ฐœ๋ฐœ์ผ์ง€ - MarketBridge

๋ชฉ๋ก ๋ณด๊ธฐ
5/8
post-thumbnail

๐Ÿ“– @SpringBootTest์—†์ด Service ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ?

Service์— ํ•„์š”ํ•œ ๋ชจ๋“  ์ •๋ณด๋“ค์„ mock ์ฒ˜๋ฆฌ ํ•ด์ฃผ๋ฉด ๊ฐ€๋Šฅํ•˜๋‹ค.
์ด๊ฒŒ ๋ฌด์Šจ๋ง์ด๋ƒ? ๋ฐ”๋กœ ์ฝ”๋“œ๋ฅผ ๋ณด์ž

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
@Builder
public class OrderCancelReturnService {

    private final DateTimeHolder dateTimeHolder;

    private final RefundService refundService;

    private final OrderDetailQueryRepository orderDetailQueryRepository;
    private final OrderDetailCommendRepository orderDetailCommendRepository;
    private final OrderQueryRepository orderQueryRepository;
    private final OrderCommendRepository orderCommendRepository;
    private final ProductRepository productRepository;
}

@Transactional
    public CancelReturnResponseDto confirmCancelReturn(CancelRequestDto cancelRequestDto, DateTimeHolder dateTimeHolder) {
        Order order = orderQueryRepository.findByOrderNo(cancelRequestDto.getOrderNo());

        Integer cancelAmount = order.changeDetailsReasonAndStatus(cancelRequestDto.getCancelReason(), ORDER_CANCEL.getCode());

        order.changeMemberCouponInfo(null);

        RefundDto refundDto = refundService.refund(order.getTid(), cancelAmount);

        return CancelReturnResponseDto.of(order, refundDto, dateTimeHolder);
    }

ํ˜„์žฌ ๋‚ด๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” Service๋Š” ๋‹ค์Œ ํ•„๋“œ๋“ค์„ ํ•„์š”๋กœ ํ•œ๋‹ค.
์ด ๋ชจ๋“  ๊ฒƒ์„ ๋ชจํ‚น์„ ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค๋Š” ๋œป์ด๊ธฐ๋„ ํ•˜๋‹ค.
(๋‹ค๋ฅธ ์ด์•ผ๊ธฐ์ง€๋งŒ ํ˜„์žฌ ์„œ๋น„์Šค์—์„œ ์ฑ…์ž„์ด ๋งŽ์•„๋ณด์ธ๋‹ค. ์ด๊ฒƒ์€ ๋‚˜์ค‘์— ๋ฆฌํŒฉํ† ๋ง ํ•˜๋„๋ก ํ•ด์•ผ๊ฒ ๋‹ค.)


์ด๋Ÿฐ์‹์œผ๋กœ Fake ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ์ฃผ์—ˆ๋‹ค.
ํŠนํžˆ ํ…Œ์ŠคํŠธ๋ฅผ ์ข€ ๋” ์›ํ™œํ•˜๊ฒŒ ์ง„ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด TestContainer๋ฅผ ๋งŒ๋“ค์—ˆ๋Š”๋ฐ ์Šคํ”„๋ง์—์„œ ์ œ๊ณตํ•˜๋Š” Config๋ฅผ ์ƒ๊ฐํ•˜๋ฉด ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šธ ๊ฒƒ์ด๋‹ค.

public class TestContainer {

    public final OrderCancelReturnService orderCancelReturnService;
    public final RefundService refundService;

    public final OrderQueryRepository orderQueryRepository;
    public final OrderCommendRepository orderCommendRepository;
    public final OrderDetailQueryRepository orderDetailQueryRepository;
    public final OrderDetailCommendRepository orderDetailCommendRepository;
    public final ProductRepository productRepository;
    public final CouponRepository couponRepository;
    public final MemberCouponRepository memberCouponRepository;

    @Builder
    public TestContainer(DateTimeHolder dateTimeHolder) {
        this.orderQueryRepository = new FakeOrderQueryRepository();
        this.orderCommendRepository = new FakeOrderCommendRepository();
        this.orderDetailQueryRepository = new FakeOrderDetailQueryRepository();
        this.orderDetailCommendRepository = new FakeOrderDetailCommendRepository();
        this.productRepository = new FakeProductRepository();
        this.couponRepository = new FakeCouponRepository();
        this.memberCouponRepository = new FakeMemberCouponRepository();

        this.refundService = new FakeRefundService(dateTimeHolder);
        this.orderCancelReturnService = OrderCancelReturnService.builder()
                .dateTimeHolder(dateTimeHolder)
                .orderDetailQueryRepository(this.orderDetailQueryRepository)
                .orderQueryRepository(this.orderQueryRepository)
                .productRepository(this.productRepository)
                .refundService(this.refundService)
                .orderCommendRepository(this.orderCommendRepository)
                .orderDetailCommendRepository(this.orderDetailCommendRepository)
                .build();

    }
}

์ข€ ๋” ์ฝ”๋“œ๋”ด์œผ๋กœ ๋“ค์–ด๊ฐ€ ๋ณด์ž

public class FakeOrderDetailCommendRepository implements OrderDetailCommendRepository {

    private Long autoGeneratedId = 0L;
    private List<OrderDetail> data = new ArrayList<>();
    
    @Override
    public void save(OrderDetail orderDetail) {
        if (orderDetail.getId() == null || orderDetail.getId() == 0) {
            ReflectionTestUtils.setField(orderDetail, "id", ++autoGeneratedId, Long.class);
            data.add(orderDetail);
        } else {
            data.removeIf(item -> Objects.equals(item.getId(), orderDetail.getId()));
            data.add(orderDetail);
        }
    }
}

์ด๋Ÿฐ์‹์œผ๋กœ Repository๋ฅผ ๋ฉ”๋ชจ๋ฆฌ ์ƒ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋ฉด ๋œ๋‹ค.
์ด๋•Œ ๋™์‹œ์„ฑ ๋ฌธ์ œ์— ๋Œ€ํ•ด ๊ถ๊ธˆํ•œ ์‚ฌ๋žŒ์ด ์žˆ์„ ์ˆ˜ ์žˆ๋Š”๋ฐ
ํ˜„์žฌ ์ด ์ฝ”๋“œ๋Š” ํ…Œ์ŠคํŠธ์—์„œ ๋ฉ”์„œ๋“œ ๋‹จ์œ„๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋™์‹œ์„ฑ์„ ๊ณ ๋ คํ•˜์ง€ ์•Š๊ณ  Long, List๋ฅผ ์‚ฌ์šฉํ•˜์˜€๋‹ค. ๋งŒ์•ฝ ๋™์‹œ์„ฑ ๋ฌธ์ œ๊ฐ€ ๊ฑฑ์ •์ด ๋œ๋‹ค๋ฉด
AtomicLong, List<..> data = Collections.synchronizedList(new ArrayList<>(); ์ด๋Ÿฐ๊ฒƒ๋“ค์„ ์ฐพ์•„๋ณด๊ณ  ๊ณต๋ถ€ํ•ด๋ณด๊ธธ ๋ฐ”๋ž€๋‹ค.

class OrderCancelReturnServiceTest {

    private OrderCancelReturnService orderCancelReturnService;
    LocalDateTime cancelDate = LocalDateTime.of(2024, 1, 31, 9, 26);

    @BeforeEach
    void init() {
        TestContainer testContainer = TestContainer.builder()
                .dateTimeHolder(TestDateTimeHolder.builder()
                        .now(cancelDate)
                        .build())
                .build();
        this.orderCancelReturnService = testContainer.orderCancelReturnService;

        Product product1 = Product.builder()
                .name("๋นต๋นต์ดํ‚ค๋ง")
                .productNo("1")
                .price(1000L)
                .thumbImg("๋นต๋นต์ด์ธ๋„ค์ผ")
                .stock(5L)
                .build();
        Product product2 = Product.builder()
                .name("์˜ฅ์ง€์–Œํ‚ค๋ง")
                .productNo("2")
                .price(2000L)
                .thumbImg("์˜ฅ์ง€์–Œ์ธ๋„ค์ผ")
                .stock(5L)
                .build();

        Coupon coupon1 = Coupon.builder()
                .name("๋นต๋นต์ดํ‚ค๋ง์ฟ ํฐ")
                .product(product1)
                .price(1000L)
                .count(10L)
                .build();
        Coupon coupon2 = Coupon.builder()
                .name("์˜ฅ์ง€์–Œํ‚ค๋ง์ฟ ํฐ")
                .product(product2)
                .price(2000L)
                .count(10L)
                .build();

        Order order = Order.builder()
                .orderNo("1")
                .tid("1")
                .totalDiscount(0L)
                .totalPrice(8000L)
                .realPrice(8000L)
                .build();

        LocalDateTime cancelledAt = LocalDateTime.of(2024, 1, 31, 3, 33);
        OrderDetail orderDetail1 = OrderDetail.builder()
                .cancelledAt(cancelledAt)
                .quantity(2L)
                .product(product1)
                .price(1000L)
                .coupon(coupon1)
                .order(order)
                .reason("๋‹จ์ˆœ๋ณ€์‹ฌ")
                .tid("1")
                .build();
        OrderDetail orderDetail2 = OrderDetail.builder()
                .cancelledAt(cancelledAt)
                .quantity(3L)
                .product(product2)
                .price(2000L)
                .coupon(coupon2)
                .order(order)
                .reason("๋‹จ์ˆœ๋ณ€์‹ฌ")
                .tid("1")
                .build();

        order.addOrderDetail(orderDetail1);
        order.addOrderDetail(orderDetail2);

        testContainer.productRepository.save(product1);
        testContainer.productRepository.save(product2);
        testContainer.orderDetailCommendRepository.save(orderDetail1);
        testContainer.orderDetailCommendRepository.save(orderDetail2);
        testContainer.orderCommendRepository.save(order);
    }

    @AfterEach
    void afterEach() {
        BaseFakeOrderDetailRepository.getInstance().clear();
    }

    @Test
    @DisplayName("์ทจ์†Œ/๋ฐ˜ํ’ˆ ํ™•์ •")
    public void confirmCancelReturn() {
        // given
        CancelRequestDto request = CancelRequestDto.builder()
                .orderNo("1")
                .cancelReason("๋‹จ์ˆœ๋ณ€์‹ฌ")
                .build();

        LocalDateTime updateTime = LocalDateTime.of(2024, 1, 31, 6, 7);
        DateTimeHolder dateTimeHolder = TestDateTimeHolder.builder()
                .updateTime(updateTime)
                .build();

        // when
        CancelReturnResponseDto result = orderCancelReturnService.confirmCancelReturn(request, dateTimeHolder);

        // then
        assertThat(result.getOrderId()).isEqualTo(1L);
        assertThat(result.getOrderNumber()).isEqualTo("1");
        assertThat(result.getTotalPrice()).isEqualTo(8000L);
        assertThat(result.getCancellationDate()).isEqualTo(updateTime);
        assertThat(result.getRefundInfo())
                .extracting("totalRefundAmount", "refundMethod", "refundProcessedAt")
                .contains(8000L, "์นด๋“œ", cancelDate);
        assertThat(result.getCancelledItems()).hasSize(2)
                .extracting("productId", "productNo", "name", "price", "quantity")
                .contains(
                        tuple(1L, "1", "๋นต๋นต์ดํ‚ค๋ง", 1000L, 2L),
                        tuple(2L, "2", "์˜ฅ์ง€์–Œํ‚ค๋ง", 2000L, 3L)
                );
    }

์‹ค์ œ ์‚ฌ์šฉํ•œ ์„œ๋น„์Šค ํ…Œ์ŠคํŠธ ์˜ˆ์‹œ์ด๋‹ค.

์œ„์—์„œ ๋ณด๋ฉด ์•Œ๊ฒ ์ง€๋งŒ Repository๋ฅผ ์ปค๋งจ๋“œ์™€ ์ฟผ๋ฆฌ๋กœ ๋‚˜๋ˆ„๋‹ˆ ์ปจ๋งจ๋“œ ์ชฝ์—์„œ ์ €์žฅํ•˜๋”๋ผ๋„ ์ฟผ๋ฆฌ์ชฝ์—์„œ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์—†์–ด ์—๋Ÿฌ๊ฐ€ ๊ณ„์† ์ƒ๊ฒผ๋‹ค. ์ด ๋ฌธ์ œ๋Š” ๋‹ค์Œ ๊ฐœ๋ฐœ์ผ์ง€์— ํฌ์ŠคํŒ… ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ๋‹น~!

profile
๋ฉˆ์ถ”์ง€ ์•Š๊ธฐ

0๊ฐœ์˜ ๋Œ“๊ธ€