【问题标题】:Functional Interface not serializable when captured from Mockito Argument captor从 Mockito 参数捕获器捕获时功能接口不可序列化
【发布时间】:2019-03-27 23:40:18
【问题描述】:

我有一些验证逻辑如下

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));
}

}

其中StringFormatValidator是一个功能接口如下

@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 中捕获的值如何失去其可序列化的行为,因为它不是模拟值,而是实际在调用站点中创建的。

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

【问题讨论】:

  • 请注意,我没有尝试实际代码,这仅基于您的 cmets 和视觉评论:class StringFormatValidation implements(误写为extends?!) IValidation 不扩展 Serializable。您是否可能将它误认为 interface StringFormatValidator 确实扩展了 Serializable?!
  • 感谢您抽出宝贵时间查看它。我已更正帖子以用工具替换扩展。我没有将Serializable 提供给IValidation,因为我只需要序列化StringFormatValidator 功能接口,该接口用作提供lambda 的参数。然后我需要在我的 equals 方法中序列化这个 lambda。
  • 您提到在参数捕获者中为StringFormatValidation 获得NotSerializableException...我错过了什么?你可以说得更详细点吗? P.S.请在回复 cmets 时使用@username,以便用户收到通知。
  • @Morfic 断言语句在 actualValidationList 上对 testFormatValidation 进行包含检查。这只是 testFormatValidationactualValidationList 中存在的所有内容的相等。 StringFormatValidation 的等号是根据 StringFormatValidator 的反序列化编写的。这无法对 StringFormatValidator 的实例进行反病毒化,该实例存在于使用参数捕获器填充的 actualValidationList 中。请注意,在测试本身中创建的testFormatValidation 的序列化使用相同的 lambda,工作正常。

标签: java unit-testing lambda mockito


【解决方案1】:

花了一些时间后,我发现了问题,我会回答我自己的问题,以便它可以帮助处于类似情况的人。 我从以下 SO 帖子中获得了见解: 1.What is the difference between a lambda and a method reference at a runtime level? 2.Equality of instance of functional interface in 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;
 }

【讨论】:

    猜你喜欢
    • 2011-04-06
    • 2017-07-15
    • 1970-01-01
    • 1970-01-01
    • 2023-01-28
    • 1970-01-01
    • 1970-01-01
    • 2019-04-04
    • 1970-01-01
    相关资源
    最近更新 更多