Back to Repositories

Testing Trade Discount Price Calculator Implementation in RuoYi Vue Pro

A comprehensive unit test suite for the TradeDiscountActivityPriceCalculator class in the RuoYi Vue Pro project, focusing on price calculation logic for trade discount activities.

Test Coverage Overview

The test suite thoroughly examines the price calculation logic for trade discount activities.

  • Tests discount calculations for multiple SKUs with different discount types (fixed price and percentage)
  • Verifies price calculations for selected and unselected items
  • Validates total price, discount price, and final payment calculations
  • Covers promotion information validation and description generation

Implementation Analysis

The testing approach utilizes Mockito for dependency isolation and JUnit for assertions.

Key implementation patterns include:
  • Mock integration with DiscountActivityApi for discount rules
  • Comprehensive price calculation verification
  • Detailed assertion chains for complex object structures
  • Test data preparation using builder pattern

Technical Details

Testing infrastructure includes:

  • JUnit Jupiter for test execution
  • Mockito for mocking external dependencies
  • BaseMockitoUnitTest as the base test class
  • Custom test utilities for random data generation
  • Assertion utilities for detailed verification

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Clear test arrangement using AAA (Arrange-Act-Assert) pattern
  • Comprehensive verification of complex business logic
  • Detailed assertions for all price components
  • Proper isolation of external dependencies
  • Well-structured test data setup

yunaiv/ruoyi-vue-pro

yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDiscountActivityPriceCalculatorTest.java

            
package cn.iocoder.yudao.module.trade.service.price.calculator;

import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.promotion.api.discount.DiscountActivityApi;
import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;

import java.util.ArrayList;

import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;

/**
 * {@link TradeDiscountActivityPriceCalculator} 的单元测试类
 *
 * @author 芋道源码
 */
@Disabled // TODO 芋艿:后续修复
public class TradeDiscountActivityPriceCalculatorTest extends BaseMockitoUnitTest {

    @InjectMocks
    private TradeDiscountActivityPriceCalculator tradeDiscountActivityPriceCalculator;

    @Mock
    private DiscountActivityApi discountActivityApi;

    @Test
    public void testCalculate() {
        // 准备参数
        TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
                .setItems(asList(
                        new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 匹配活动,且已选中
                        new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(false) // 匹配活动,但未选中
                ));
        TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
                .setType(TradeOrderTypeEnum.NORMAL.getType())
                .setPrice(new TradePriceCalculateRespBO.Price())
                .setPromotions(new ArrayList<>())
                .setItems(asList(
                        new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
                                .setPrice(100),
                        new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(false)
                                .setPrice(50)
                ));
        // 保证价格被初始化上
        TradePriceCalculatorHelper.recountPayPrice(result.getItems());
        TradePriceCalculatorHelper.recountAllPrice(result);

        // mock 方法(限时折扣活动)
        when(discountActivityApi.getMatchDiscountProductListBySkuIds(eq(asSet(10L, 20L)))).thenReturn(asList(
                randomPojo(DiscountProductRespDTO.class, o -> o.setActivityId(1000L)
                        .setActivityName("活动 1000 号").setSkuId(10L)
                        .setDiscountType(PromotionDiscountTypeEnum.PRICE.getType()).setDiscountPrice(40)),
                randomPojo(DiscountProductRespDTO.class, o -> o.setActivityId(2000L)
                        .setActivityName("活动 2000 号").setSkuId(20L)
                        .setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()).setDiscountPercent(60))
        ));
        // 10L: 100 * 2 - 40 * 2 = 120
        // 20L:50 * 3 - 50 * 3 * 0.4 = 90

        // 调用
        tradeDiscountActivityPriceCalculator.calculate(param, result);
        // 断言:Price 部分
        TradePriceCalculateRespBO.Price price = result.getPrice();
        assertEquals(price.getTotalPrice(), 200);
        assertEquals(price.getDiscountPrice(), 80);
        assertEquals(price.getPointPrice(), 0);
        assertEquals(price.getDeliveryPrice(), 0);
        assertEquals(price.getCouponPrice(), 0);
        assertEquals(price.getPayPrice(), 120);
        assertNull(result.getCouponId());
        // 断言:SKU 1
        assertEquals(result.getItems().size(), 2);
        TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
        assertEquals(orderItem01.getSkuId(), 10L);
        assertEquals(orderItem01.getCount(), 2);
        assertEquals(orderItem01.getPrice(), 100);
        assertEquals(orderItem01.getDiscountPrice(), 80);
        assertEquals(orderItem01.getDeliveryPrice(), 0);
        assertEquals(orderItem01.getCouponPrice(), 0);
        assertEquals(orderItem01.getPointPrice(), 0);
        assertEquals(orderItem01.getPayPrice(), 120);
        // 断言:SKU 2
        TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
        assertEquals(orderItem02.getSkuId(), 20L);
        assertEquals(orderItem02.getCount(), 3);
        assertEquals(orderItem02.getPrice(), 50);
        assertEquals(orderItem02.getDiscountPrice(), 60);
        assertEquals(orderItem02.getDeliveryPrice(), 0);
        assertEquals(orderItem02.getCouponPrice(), 0);
        assertEquals(orderItem02.getPointPrice(), 0);
        assertEquals(orderItem02.getPayPrice(), 90);
        // 断言:Promotion 部分
        assertEquals(result.getPromotions().size(), 1);
        TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0);
        assertEquals(promotion01.getId(), 1000L);
        assertEquals(promotion01.getName(), "活动 1000 号");
        assertEquals(promotion01.getType(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType());
        assertEquals(promotion01.getTotalPrice(), 200);
        assertEquals(promotion01.getDiscountPrice(), 80);
        assertTrue(promotion01.getMatch());
        assertEquals(promotion01.getDescription(), "限时折扣:省 0.80 元");
        TradePriceCalculateRespBO.PromotionItem promotionItem01 = promotion01.getItems().get(0);
        assertEquals(promotion01.getItems().size(), 1);
        assertEquals(promotionItem01.getSkuId(), 10L);
        assertEquals(promotionItem01.getTotalPrice(), 200);
        assertEquals(promotionItem01.getDiscountPrice(), 80);
    }

}