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

如何结合Mockito与AssertJ的递归比较验证方法调用参数?

问题:结合Mockito与AssertJ递归比较验证方法调用参数

在测试场景中,针对未实现hashCode()/equals()方法的数据对象,AssertJ提供了便捷的递归比较API:

Assertions.assertThat(result)
    .usingRecursiveComparison()
    .ignoringFields("id")
    .isEqualTo(expected)

而验证方法调用情况时,通常使用Mockito的如下写法:

Mockito.verify(mock, times(2)).method(eq(arg1), eq(arg2));

现在希望将两者结合,在Mockito的verify验证中,用AssertJ的递归比较逻辑来匹配方法参数,比如实现类似这样的调用:

Mockito.verify(mock, times(2)).method(usingRecursiveComparison(arg1));

请问是否可以通过Mockito+AssertJ实现该需求,或是有其他支持此类API的工具库?


解决方案

方法一:自定义Mockito ArgumentMatcher封装AssertJ递归比较

Mockito支持自定义ArgumentMatcher,可以直接把AssertJ的递归比较逻辑封装进去,实现复用:

  1. 编写自定义Matcher类:
import org.assertj.core.api.Assertions;
import org.mockito.ArgumentMatcher;

public class RecursiveEqualsMatcher<T> implements ArgumentMatcher<T> {
    private final T expected;
    private final String[] ignoredFields;

    private RecursiveEqualsMatcher(T expected, String... ignoredFields) {
        this.expected = expected;
        this.ignoredFields = ignoredFields;
    }

    // 静态工厂方法,模拟AssertJ的API风格
    public static <T> RecursiveEqualsMatcher<T> usingRecursiveComparison(T expected) {
        return new RecursiveEqualsMatcher<>(expected);
    }

    // 支持忽略指定字段的重载方法
    public static <T> RecursiveEqualsMatcher<T> usingRecursiveComparison(T expected, String... ignoredFields) {
        return new RecursiveEqualsMatcher<>(expected, ignoredFields);
    }

    @Override
    public boolean matches(T actual) {
        try {
            Assertions.assertThat(actual)
                    .usingRecursiveComparison()
                    .ignoringFields(ignoredFields)
                    .isEqualTo(expected);
            return true;
        } catch (AssertionError e) {
            // 断言失败则返回false,Mockito会判定参数不匹配
            return false;
        }
    }

    @Override
    public String toString() {
        // 自定义描述,验证失败时能输出清晰的错误信息
        return "recursive equals to: " + expected + 
               (ignoredFields.length > 0 ? " (ignoring fields: " + String.join(", ", ignoredFields) + ")" : "");
    }
}
  1. 测试中的使用方式:
// 基础递归比较
Mockito.verify(mock, times(2)).method(RecursiveEqualsMatcher.usingRecursiveComparison(arg1));

// 忽略指定字段的递归比较
Mockito.verify(mock, times(2)).method(RecursiveEqualsMatcher.usingRecursiveComparison(arg1, "id", "createTime"));

方法二:使用Mockito的argThat内联实现

如果只是临时场景,不想编写自定义Matcher,可以直接用Mockito的argThat方法内联AssertJ的断言逻辑:

Mockito.verify(mock, times(2)).method(argThat(actual -> {
    try {
        Assertions.assertThat(actual)
                .usingRecursiveComparison()
                .ignoringFields("id")
                .isEqualTo(arg1);
        return true;
    } catch (AssertionError e) {
        return false;
    }
}));

这种方式灵活但复用性较差,适合单次使用的场景。

方法三:使用官方扩展库AssertJ-Mockito

AssertJ官方提供了assertj-mockito扩展库,直接支持在Mockito验证中使用AssertJ的所有断言逻辑(包括递归比较),无需自己实现Matcher:

使用示例:

import org.assertj.mockito.api.Assertions;

// 验证方法调用次数及参数匹配
Assertions.assertThat(mock).hasBeenCalled().times(2)
    .withMethodCall(method -> method.targetMethod(
        // 直接使用AssertJ的递归比较断言参数
        Assertions.assertThat(arg1).usingRecursiveComparison().ignoringFields("id")
    ));

这个库是官方维护的,兼容性和稳定性都有保障。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 01:50:14