# Business Helper 测试完整示例

> 以 `AutoCompleteWorkHelper` 为例，展示 business 帮助类的完整测试写法。

```java
package kd.mmc.sfc.business;

import kd.bd.mpdm.common.sfc.assist.ProcessStatus;
import kd.bd.sbd.enums.sfc.SequenceTypeEnum;
import kd.bos.dataentity.OperateOption;
import kd.bos.dataentity.entity.DynamicObject;
import kd.bos.dataentity.entity.DynamicObjectCollection;
import kd.bos.entity.operate.result.OperationResult;
import kd.bos.org.utils.Consts;
import kd.bos.servicehelper.operation.OperationServiceHelper;
import kd.bos.test.ext.annotaions.UnittestCaseInfo;
import kd.bos.unittest.AbstractJunitNoDependenciesResManagerTest;
import kd.bos.unittest.mock.DynamicObjectMocker;
import kd.bos.unittest.mock.ReflectHelper;
import kd.mmc.sfc.common.consts.SendWorkConst;
import kd.mmc.sfc.common.outsourcesend.OutsourceSendConsts;
import kd.mmc.sfc.common.processplan.ProcessPlanConsts;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockedStatic;

import java.math.BigDecimal;
import java.util.*;

import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

/**
 * 自动完工帮助类单测
 * @author zhang_san
 * @version 1.0
 * @date 2025/4/14 9:18
 */
public class AutoCompleteWorkHelperTest extends AbstractJunitNoDependenciesResManagerTest {

    // ========== 1. 声明 MockedStatic ==========
    MockedStatic<OperationServiceHelper> operationServiceHelper;
    MockedStatic<ProcessPlanHelper> processPlanHelper;
    private MockedStatic<ProcessReviewHelper> processReviewHelper;

    // ========== 2. @Before 初始化 ==========
    @Before
    public void before() {
        operationServiceHelper = mockStatic(OperationServiceHelper.class);
        processPlanHelper = mockStatic(ProcessPlanHelper.class);
        processReviewHelper = mockStatic(ProcessReviewHelper.class);
    }

    // ========== 3. @After 关闭 ==========
    @After
    public void after() {
        operationServiceHelper.close();
        processPlanHelper.close();
        processReviewHelper.close();
    }

    // ========== 4. reset 方法 ==========
    public void reset() {
        operationServiceHelper.reset();
        processPlanHelper.reset();
        processReviewHelper.reset();
    }

    // ========== 5. 测试 public static 方法 ==========

    @UnittestCaseInfo(
        author = "zhang_san <zhang_san@kingdee.com>",
        title = "Invoke bill operate success",
        targetClass = "kd.mmc.sfc.business.AutoCompleteWorkHelper",
        targetMethod = "invokebilloperate",
        lastUpdateTime = "2025-04-14 09:27:03",
        lastUpdateAuthor = "zhang_san <zhang_san@kingdee.com>",
        methodSignature = "protected static OperationResult invokeBillOperate(...)",
        testPoints = {"Functionality"},
        description = "Invoke bill operate success"
    )
    @Test
    public void invokeBillOperate_Success() {
        //step 构建查询返回数据
        Set<String> billIds = new HashSet<>(Arrays.asList("1", "2"));
        Set<String> entryIds = new HashSet<>(Collections.singletonList("1"));
        Map<String, Date> dateMap = new HashMap<>();
        dateMap.put("1", new Date());

        OperationResult opResult = new OperationResult();
        opResult.setSuccess(true);
        operationServiceHelper.when(() -> OperationServiceHelper.executeOperate(
                anyString(), anyString(), any(Object[].class), any(OperateOption.class)))
            .thenReturn(opResult);

        //step 执行被测方法
        OperationResult result = AutoCompleteWorkHelper.invokeBillOperate(
            "operation", billIds, entryIds, dateMap, "formId");

        //assert 断言返回结果
        assertTrue(result.isSuccess());
        reset();
    }

    @Test
    public void invokeBillOperate_Exception() {
        //step 构建异常场景
        Set<String> billIds = new HashSet<>(Arrays.asList("1", "2"));
        Set<String> entryIds = new HashSet<>(Collections.singletonList("1"));
        Map<String, Date> dateMap = new HashMap<>();
        dateMap.put("1", new Date());

        operationServiceHelper.when(() -> OperationServiceHelper.executeOperate(
                anyString(), anyString(), any(Object[].class), any(OperateOption.class)))
            .thenThrow(new RuntimeException("Exception"));

        //step 执行被测方法（异常被内部捕获）
        OperationResult result = AutoCompleteWorkHelper.invokeBillOperate(
            "operation", billIds, entryIds, dateMap, "formId");

        //assert 异常被捕获后仍返回成功
        assertTrue(result.isSuccess());
        reset();
    }

    // ========== 6. 测试 private static 方法（用 ReflectHelper）==========

    @Test
    public void testAutoProcessPlanCompleteWork_WhenProPlanBillsIsEmpty() {
        //step 模拟 proPlanBills 为空
        Set<Long> proPlanEntryIds = new HashSet<>();
        proPlanEntryIds.add(1001L);
        Map<Long, Date> allEndTimeMap = new HashMap<>();
        allEndTimeMap.put(1001L, new Date());

        //mock getProPlanBillsByEntryIds 返回空数组
        processPlanHelper.when(() -> ProcessPlanHelper.getProPlanBillsByEntryIds(
                proPlanEntryIds, Arrays.asList(ProcessStatus.STARTWORKER, ProcessStatus.SENDORDER)))
            .thenReturn(new DynamicObject[0]);

        //step 通过反射调用 private static 方法
        ReflectHelper.invokeStaticMethod(AutoCompleteWorkHelper.class,
            "autoProcessPlanCompleteWork", proPlanEntryIds, allEndTimeMap);

        //assert 不应调用 invokeBillOperate
        operationServiceHelper.verify(() -> OperationServiceHelper.executeOperate(
            anyString(), anyString(), any(Object[].class), any(OperateOption.class)), never());
    }

    // ========== 7. 复杂数据构造示例 ==========

    @Test
    public void testAutoProcessPlanCompleteWork_MeetConditions() {
        //step 构建满足条件的工序计划数据
        Set<Long> proPlanEntryIds = new HashSet<>();
        proPlanEntryIds.add(1001L);
        Map<Long, Date> allEndTimeMap = new HashMap<>();
        allEndTimeMap.put(1001L, new Date());

        // 构建分录集合
        DynamicObjectCollection entrys = new DynamicObjectCollection();
        entrys.add(new DynamicObjectMocker()
            .add(ProcessPlanConsts.ENTRY.PROCESSTATUS, ProcessStatus.STARTWORKER)
            .add(ProcessPlanConsts.ENTRY.TOBEINSPECTQTY, new BigDecimal("0"))
            .add(ProcessPlanConsts.ENTRY.TOBEREWORKEDQTY, new BigDecimal("0"))
            .add(ProcessPlanConsts.ENTRY.COMPLETEQTY, new BigDecimal("10"))
            .add(ProcessPlanConsts.ENTRY.PROCESSQTY, new BigDecimal("10"))
            .add(OutsourceSendConsts.ENTRY.ID, 1001L)
            .add(ProcessPlanConsts.ENTRY.PROCESSSEQUENCE, "1")
            .add(ProcessPlanConsts.ENTRY.PROCESSNUMBER, "1001")
            .add(ProcessPlanConsts.ENTRY.SEQUENCETYPE, SequenceTypeEnum.MAIN.getType())
            .getObject());

        // 构建单据（包含分录）
        DynamicObject proPlanBill = new DynamicObjectMocker()
            .add(Consts.ID, 1L)
            .add(ProcessPlanConsts.BILLTYPE_ID, 100L)
            .add(ProcessPlanConsts.ENTRY.ENTRYENTITY_ENTITY, entrys)
            .getObject();

        //mock
        processPlanHelper.when(() -> ProcessPlanHelper.getProPlanBillsByEntryIds(
                proPlanEntryIds, Arrays.asList(ProcessStatus.STARTWORKER, ProcessStatus.SENDORDER)))
            .thenReturn(new DynamicObject[]{proPlanBill});

        //step 执行
        ReflectHelper.invokeStaticMethod(AutoCompleteWorkHelper.class,
            "autoProcessPlanCompleteWork", proPlanEntryIds, allEndTimeMap);

        //assert 应调用 executeOperate 2次
        operationServiceHelper.verify(() -> OperationServiceHelper.executeOperate(
            anyString(), anyString(), any(Object[].class), any(OperateOption.class)), times(2));
    }
}
```

## 要点总结

1. **MockedStatic 三板斧**：声明 → @Before 初始化 → @After 关闭
2. **reset()** 方法在每个 @Test 末尾调用，防止状态污染
3. **private/protected 方法**用 `ReflectHelper` 反射调用
4. **DynamicObjectMocker** 链式构造单据 + 分录的嵌套结构
5. **@UnittestCaseInfo** 注解记录完整的测试元信息
6. **断言方式**：assertEquals / assertTrue / assertFalse / assertNotNull + verify 验证调用次数
