You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Mockito验证方法参数:匹配指定ID且仅校验复杂对象非空字段

问题

需要调整Mockito的verify方法实现以下校验:

  • 第一个参数与预期值精确匹配
  • 第二个参数为List<Profile>类型,其中Profile实例可能存在部分字段为null,仅验证已赋值的非空字段,忽略未赋值的null字段
  • 确认被Mock的valid方法调用时传入的userId正确

当前相关代码如下:

现有校验方法

public void verifyProfileOnce(UserId userId, Profile profile) {
    verify(profileClientMockBean, atLeastOnce()).valid(eq(userId), argThat(capturedList -> capturedList.stream().filter(capturedProfile -> profile.equals(capturedProfile)).count() == 1));
}

被Mock方法签名

void valid(UserId userId, List<Profile> profile);

测试调用示例

profileMock.verifyProfileOnce(userId.toString(),builder().name(TEST).email("test@test").address("qwerty").comment("First test").build());
解决方案

完全可以实现需求,核心是自定义字段匹配逻辑,忽略Profile中的null字段,仅对比已赋值的属性。以下是两种可行实现方式:

方式一:自定义ArgumentMatcher(推荐,可复用)

创建专门的匹配器类,封装字段对比逻辑:

import org.mockito.ArgumentMatcher;

public class ProfilePartialMatcher implements ArgumentMatcher<Profile> {
    private final Profile expected;

    public ProfilePartialMatcher(Profile expected) {
        this.expected = expected;
    }

    @Override
    public boolean matches(Profile actual) {
        // 仅对比预期对象中非null的字段
        if (expected.getName() != null && !expected.getName().equals(actual.getName())) {
            return false;
        }
        if (expected.getEmail() != null && !expected.getEmail().equals(actual.getEmail())) {
            return false;
        }
        if (expected.getAddress() != null && !expected.getAddress().equals(actual.getAddress())) {
            return false;
        }
        if (expected.getComment() != null && !expected.getComment().equals(actual.getComment())) {
            return false;
        }
        // 可根据Profile的其他字段扩展对比逻辑
        return true;
    }
}

修改校验方法,结合自定义匹配器验证列表中的Profile:

public void verifyProfileOnce(UserId userId, Profile expectedProfile) {
    verify(profileClientMockBean, atLeastOnce()).valid(
        eq(userId),
        argThat(capturedList -> 
            // 验证列表中存在至少一个符合条件的Profile
            capturedList.stream().anyMatch(actual -> new ProfilePartialMatcher(expectedProfile).matches(actual))
            // 若需要恰好一个匹配项,替换为:
            // capturedList.stream().filter(actual -> new ProfilePartialMatcher(expectedProfile).matches(actual)).count() == 1
        )
    );
}

方式二:Lambda内直接实现逻辑(快速验证)

如果不需要复用匹配逻辑,可直接在argThat的Lambda中编写对比代码:

public void verifyProfileOnce(UserId userId, Profile expectedProfile) {
    verify(profileClientMockBean, atLeastOnce()).valid(
        eq(userId),
        argThat(capturedList -> 
            capturedList.stream().anyMatch(actual -> {
                boolean isMatch = true;
                if (expectedProfile.getName() != null) {
                    isMatch &= expectedProfile.getName().equals(actual.getName());
                }
                if (expectedProfile.getEmail() != null) {
                    isMatch &= expectedProfile.getEmail().equals(actual.getEmail());
                }
                if (expectedProfile.getAddress() != null) {
                    isMatch &= expectedProfile.getAddress().equals(actual.getAddress());
                }
                if (expectedProfile.getComment() != null) {
                    isMatch &= expectedProfile.getComment().equals(actual.getComment());
                }
                return isMatch;
            })
        )
    );
}

注意事项

  • 确保UserId类正确实现了equals和hashCode方法,否则eq(userId)的匹配会失效
  • 若Profile字段较多,优先选择方式一,代码更易维护和扩展
  • 根据测试需求,将anyMatch替换为count() == 1可验证列表中恰好存在一个匹配的Profile

内容的提问来源于stack exchange,提问作者epb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 03:27:39