Функциональный интерфейс не сериализуется при захвате из захвата аргумента Mockito - PullRequest
0 голосов
/ 20 марта 2019

У меня есть некоторая логика для проверки следующим образом

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 для сравнения лямбды на сериализованных байтах (не уверен ни в каком другом лучшем способе на данный момент). У меня есть следующий модульный тест, который работает как ожидалось

@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 в аргументе captor.

Я не понимаю, как захваченное значение в аргументе Mockito caprtor теряет свое сериализуемое поведение, учитывая, что это не поддельное значение, а фактически созданное на сайте вызова.

Примечание. Я упростил общие подписи и имена, чтобы сосредоточиться только на проблеме.

1 Ответ

0 голосов
/ 27 марта 2019

Потратив некоторое время, я нашел проблему и отвечал на свой вопрос, чтобы он помог кому-то в подобной ситуации. Я получил информацию из следующих сообщений: 1. В чем разница между лямбда и ссылкой на метод на уровне времени выполнения? 2. Равенство экземпляра функционального интерфейса в Java Были две проблемы, с которыми я столкнулся. Во-первых, исходная проблема, упомянутая в приведенном выше вопросе java.io.NotSerializableException: Non-serializable lambda. У меня сложилось впечатление, что захват аргумента из Mockito каким-то образом мешает, и полученный лямбда-аргумент больше не сериализуем. Однако это было больше связано с тем, как сериализация лямбда в Java происходит в целом. Я до сих пор не полностью понимаю внутренности, но исключение было разрешено в одной из тех ситуаций, когда вы действительно не знаете, что сработало. Затем я обнаружил, что равно не удалось, потому что сериализованные значения содержат сайт вызова. Следовательно, 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;
 }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...