JUnit测试无法在Jenkins中使用PlatformResourceBundleLocator从多个JAR文件中聚合Hibernate Validator属性,但不能在本地使用。

问题描述 投票:0回答:1

我有一个在Java 11上运行的Gradle Spring Boot应用,使用的是 Hibernate验证器. 最近,我开始使用多个JAR,它们定义了自己的 ValidationMessages.properties 文件的默认消息,用于自己的自定义验证注解。 为了支持这一点,我使用了在 平台资源捆绑定位器 以汇总 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 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 文件中包含了匹配的密钥。 然而,当我的项目测试在构建服务器上通过 詹金斯 持续集成工具,上述测试失败。

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

深入研究一下这个问题,发现只有其中的一个 ValidationMessages.properties 正在使用,而另一个没有。 我的猜测是测试在Jenkins中的运行顺序与本地不同。 出于某种原因,测试顺序一定是以某种方式与消息属性的使用发生了关系,尽管事实上我可以通过日志记录确认自定义的 PlatformResourceBundleLocator 被用于上述测试。 支持这个理论的是,当我配置Jenkins只运行单个测试而不是所有测试时,它就会通过。

虽然我没有在任何地方看到它的文档,但从 Hibernate验证器源码 我看到在两种情况下,资源捆绑聚合是被禁用的:在Google App Engine环境中和当Hibernate Validator作为Java 9命名的模块运行时。 这两种情况都不是,我已经通过日志确认聚合是启用的。

为什么会发生这种情况,我如何修复它,使验证框架使用我的聚合验证消息,无论我的测试在哪里运行?

我目前使用的是Spring Boot 2.2.4.RELEASE和Hibernate Validator 6.0.18.Final。

java spring-boot bean-validation hibernate-validator resourcebundle
1个回答
0
投票

这个问题的根本原因是 ResourceBundle默认情况下是缓存的。 根据 ResourceBundle JavaDocs:

资源包实例 getBundle 工厂方法默认被缓存,如果已经被缓存,工厂方法会多次返回同一个资源捆绑实例。

由于这种缓存,无论哪种测试首先导致Hibernate Validator加载了 ValidationMessages.properties 将决定哪些 ResourceBundle 被用于所有后续的测试。 在Jenkins上,看起来一个没有使用自定义验证配置的测试必须先运行。 这将导致一个非聚合的 PropertyResourceBundle 缓存,用于 ValidationMessages而不是理想的 AggregateResourceBundle. 当 PlatformResourceBundleLocator 加载资源捆绑,聚合逻辑被忽略,因为缓存的 ResourceBundle 用来代替。

修复方法很简单。 ResourceBundle 有一个clearCache方法来清除缓存,它可以在Hibernate Validator会检索bundle来创建验证消息之前被调用。

@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 捆绑,他们同样需要事先清除缓存,首先驱逐聚合的捆绑。

© www.soinside.com 2019 - 2024. All rights reserved.