Тестам JUnit не удается агрегировать свойства Hibernate Validator из нескольких файлов JAR с помощью PlatformResourceBundleLocator в Jenkins, но не локально - PullRequest
0 голосов
/ 02 мая 2020

У меня приложение Gradle Spring Boot, работающее на Java 11 с использованием Hibernate Validator . Недавно я начал использовать несколько JAR-файлов, которые определяют свои собственные файлы ValidationMessages.properties с сообщениями по умолчанию для своих собственных пользовательских аннотаций проверки. Для поддержки этого я использую встроенную функциональность PlatformResourceBundleLocator для объединения ValidationMessages.properties файлов из нескольких файлов JAR в один пакет:

@Configuration
public class ValidationConfig {
    @Bean
    public LocalValidatorFactoryBean validator() {
        ResourceBundle.clearCache();

        PlatformResourceBundleLocator resourceBundleLocator =
                new PlatformResourceBundleLocator(ResourceBundleMessageInterpolator.USER_VALIDATION_MESSAGES, null, true);

        LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
        factoryBean.setMessageInterpolator(new ResourceBundleMessageInterpolator(resourceBundleLocator));
        return factoryBean;
    }
}

jar1: ValidationMessages.properties

com.example.CustomValidation1.message=My custom message 1

jar2: ValidationMessages.properties

com.example.CustomValidation2.message=My custom message 2

Я использую эти проверки в моих классах обслуживания с использованием аннотации Spring @Validated:

@Validated
@Service
public class MyService {
  public void myMethod(@Valid @CustomValidation1 @CustomValidation2 MyInput input) {
    // do something with input
  }
}

Эта функция работает должным образом, когда я запускаю приложение напрямую.

В проекте имеется большое количество модульных и интеграционных тестов, которые я запускаю из Gradle, некоторые из которых проверяют сообщение проверки, например:

@RunWith(SpringRunner.class)
@Import({MyService.class, ValidationConfig.class, ValidationAutoConfiguration.class})
public class MyServiceTest {
    @Autowired
    private MyService service;

    @Test
    public void testValidation() {
        MyInput input = inputWhichFailsBothValidations();
        try {
            service.myMethod(updateRequests);
            fail("Expected a ConstraintViolationException to be thrown");
        } catch (ConstraintViolationException e) {
            assertEquals(Set.of("My custom message 1", "My custom message 2"), e.getConstraintViolations().stream()
                    .map(ConstraintViolation::getMessage).collect(Collectors.toSet()));
        }
    }
}

Проект также содержит ряд более старых тестов, некоторые из которых используют валидации, но не полагаются на новую функциональность совокупного сообщения и не используют ее. Ни один из более старых тестов не основывается на предыдущем неагрегировании ValidationMessages.properties, хотя, поскольку они предшествуют его использованию, они не обязательно его конфигурируют.

Тесты работают, как и ожидалось, локально, при этом сообщения проверки выводятся из какой бы файл JAR ValidationMessages.properties не содержал соответствующий ключ. Однако, когда тесты моего проекта выполняются на сервере сборки с помощью инструмента непрерывной интеграции Jenkins , вышеприведенный тест не пройден.

java.lang.AssertionError: 
Expected :[My custom message 1, My custom message 2]
Actual   :[My custom message 1, {com.example.CustomValidation2.message}]

Немного окунувшись в проблему, оказывается, что используется только один из ValidationMessages.properties, а другой - нет. Я предполагаю, что тесты в Дженкинсе выполняются в другом порядке, нежели локально. По какой-то причине порядок тестирования должен каким-то образом зависеть от использования свойства сообщения, несмотря на тот факт, что я могу подтвердить посредством регистрации, что настроенный PlatformResourceBundleLocator используется для вышеуказанного теста. Поддержка этой теории заключается в том, что когда я настраиваю Jenkins для запуска только одного теста, а не всех тестов, он проходит.

Хотя я нигде не вижу его документированного, из исходного кода Hibernate Validator Я вижу, что агрегация пакетов ресурсов отключена в двух случаях: в среде Google App Engine и при запуске Hibernate Validator с именем Java 9 с именованным модулем. Ни один из этих случаев не подходит, и я подтвердил с помощью регистрации, что агрегация включена.

Почему это происходит, и как я могу это исправить, чтобы инфраструктура проверки использовала мои агрегированные сообщения проверки независимо от того, где мои тесты запущены?

В данный момент я нахожусь на Spring Boot 2.2.4.RELEASE с Hibernate Validator 6.0.18.Final.

1 Ответ

0 голосов
/ 02 мая 2020

Эта основная причина этой проблемы заключается в том, что ResourceBundle s кэшируются по умолчанию. Согласно ResourceBundle JavaDocs :

Экземпляры комплекта ресурсов, созданные фабричными методами getBundle, кэшируются по умолчанию, а фабричные методы возвращают один и тот же экземпляр комплекта ресурсов несколько раз, если он был кэширован.

Из-за этого кэширования в зависимости от того, какой тест сначала вызовет Hibernate Validator, загрузку ValidationMessages.properties определит, какой ResourceBundle будет использоваться для всех последующих тестов. На Jenkins, похоже, что тест, который не использует настроенную конфигурацию проверки, должен выполняться в первую очередь. Это приводит к кэшированию неагрегированного PropertyResourceBundle для ValidationMessages, а не к желаемому AggregateResourceBundle. Когда PlatformResourceBundleLocator загружает пакет ресурсов, логика агрегирования c игнорируется, поскольку вместо него используется кэшированный ResourceBundle.

Исправление является простым. ResourceBundle имеет метод clearCache для очистки кэша, который может быть вызван до того, как Hibernate Validator получит пакет для создания сообщения проверки:

@RunWith(SpringRunner.class)
@Import({MyService.class, ValidationConfig.class, ValidationAutoConfiguration.class})
public class MyServiceTest {
    @Autowired
    private MyService service;

    @Before
    public void setup() {
        ResourceBundle.clearCache();
    }

    @Test
    public void testValidation() {
        MyInput input = inputWhichFailsBothValidations();
        try {
            service.myMethod(updateRequests);
            fail("Expected a ConstraintViolationException to be thrown");
        } catch (ConstraintViolationException e) {
            assertEquals(Set.of("My custom message 1", "My custom message 2"), e.getConstraintViolations().stream()
                    .map(ConstraintViolation::getMessage).collect(Collectors.toSet()));
        }
    }
}

Обратите внимание, что если какой-либо из других тестов ожидал агрегированный пакет ValidationMessages.properties, им также необходимо предварительно очистить кэш, чтобы сначала удалить агрегированный пакет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...