首页 > 解决方案 > 从 Mockito 参数捕获器捕获时功能接口不可序列化

问题描述

我有一些验证逻辑如下

public interface IValidation {
   void validate();
}

public class ParameterValidator {
   public void validate(IValidation... validations) {
      for (IValidation validation : validations) {
        validation.validate();
      }
   }
}

验证之一是在 StringFormat 上,如下所示

public class StringFormatValidation implements IValidation {
   public StringFormatValidation(StringFormatValidator stringFormatValidator, String param) {
      ...
   }

   @Override
    public boolean equals(Object obj) {
        if (obj == this) return true;
        if (!(obj instanceof StringFormatValidation)) return false;
        StringFormatValidation other = (StringFormatValidation) obj;
        if (!Objects.equals(this.param, other.param)) return false;
        return 
     Arrays.equals(SerializationUtils.serialize(this.stringFormatValidator), 
     SerializationUtils.serialize(other.stringFormatValidator));
}

}

whereStringFormatValidator是一个功能接口如下

@FunctionalInterface
public interface StringFormatValidator extends Serializable {
    boolean apply(String arg);
}

我已经覆盖了equals来比较序列化字节上的lambda(到目前为止还不确定其他更好的方法)。我有一个按预期工作的以下单元测试

@Test
public void testEquality() {
  StringFormatValidation testFormatValidation1 = new 
  StringFormatValidation(StringFormatValidators::isCommaSeparated,"test1");
  StringFormatValidation testFormatValidation2 = new 
  StringFormatValidation(StringFormatValidators::isCommaSeparated,"test2");;
  Assert.assertEquals(testFormatValidation1, testFormatValidation2);
}

但是当我尝试如下测试调用站点时,

@MockBean
ParameterValidator parameterValidator;

@Captor
ArgumentCaptor<IValidation> argumentCaptor;

@Test
public void testParameterValidations() {
    testResource.doSomething(parameter1, "testParam");
    Mockito.verify(parameterValidator).validate(argumentCaptor.capture());
    List<IValidation> actualValidationList = argumentCaptor.getAllValues();
    StringFormatValidation testFormatValidation = new 
    StringFormatValidation(StringFormatValidators::isCommaSeparated, 
    "testParam");
    Assert.assertTrue(actualValidationList.contains(testFormatValidation));
}

我得到参数捕获器java.io.NotSerializableException: Non-serializable lambda中的值的异常。StringFormatValidation

我不明白 Mockito 的参数 caprtor 中捕获的值如何失去它的可序列化行为,因为它不是模拟值,而是实际在调用站点中创建的。

注意:我已经简化了整体签名和命名,以便只关注手头的问题。

标签: javaunit-testinglambdamockito

解决方案


花了一些时间后,我发现了问题,我会回答我自己的问题,以便它可以帮助处于类似情况的人。我从以下 SO 帖子中获得了见解: 1. lambda 和运行时级别的方法引用有什么区别? 2. java中函数接口实例的相等性 我遇到了两个问题。一、上面问题中提到的原问题java.io.NotSerializableException: Non-serializable lambda. 我的印象是,来自 Mockito 的参数捕获会以某种方式干扰,并且捕获的 lambda 参数不再是可序列化的。然而,它更多地与 java 中 lambda 的序列化一般如何发生有关。我仍然不完全了解内部结构,但是当您真的不知道什么有效时,在其中一种情况下解决了异常。然后我遇到equals失败,因为序列化值包含调用站点。因此StringFormatValidation testFormatValidation1 = new StringFormatValidation(StringFormatValidators::isCommaSeparated,"test1");,在测试类中创建的将具有测试类的路径,而主类中的相同构造将具有它的路径。我通过提取StringFormatValidators::isCommaSeparated静态变量并从所有呼叫站点使用它来解决这个问题。

 public class StringFormatValidators {

    private static boolean isCommaSeparatedFn(String arg) {
       String COMMA_SEPARATED_STRINGS = "^[a-zA-Z0-9]+[a-zA-Z0-9-_:]*(,[a-zA-Z0-9]+ 
        [a-zA-Z0-9-_:]*)*$";
       Pattern COMMA_SEPARATED_STRINGS_PATTERN = 
       Pattern.compile(COMMA_SEPARATED_STRINGS);
       return arg != null && COMMA_SEPARATED_STRINGS_PATTERN.matcher(arg).find();
    }

    public static final StringFormatValidator isCommaSeparated = 
       StringFormatValidators::isCommaSeparatedFn;
 }

推荐阅读