从Mockito Argument捕获器捕获时,功能接口不可序列化

时间:2019-03-20 09:34:36

标签: java unit-testing lambda 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));
}

}

其中StringFormatValidator是如下的功能接口

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

我已经重写了等于以比较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中捕获的值如何释放它的可序列化行为,因为它不是模拟值,而是在调用站点中实际创建的。

  

注意:我简化了总体签名和命名,以仅将重点放在眼前的问题上。

1 个答案:

答案 0 :(得分:0)

花了一些时间后,我发现了问题,我将回答自己的问题,这样可以帮助处于类似情况的人。 我从以下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的序列化通常如何发生有关。 我仍然不完全了解内部原理,但是在其中一种情况下,当您真的不知道起作用的情况时,该异常已解决。 然后,我遇到了相等失败的问题,因为序列化的值包含调用站点。因此,在测试类中创建的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;
 }