【问题标题】:How to test a specific javax custom validator that uses JPA repositories?如何测试使用 JPA 存储库的特定 javax 自定义验证器?
【发布时间】:2019-10-29 22:42:38
【问题描述】:

过去几个月我一直在从事 Java 项目(1.8 和 11)。我们正在尝试将模型中的每个验证编码为自定义 javax 验证,我认为这是迄今为止我发现的“最佳”方法(一如既往地接受新建议)...... 但是,问题是当这个验证器需要从数据库和多个验证器中获取同一模型/实体的数据时。

实体

@DuplicatedUsername
@Getter
@Setter
public class User {
    private UUID id;
    private String username;
    public User(username) {
        this.username = username;
    }
}

验证器

public class DuplicatedUsernameValidator implements ConstraintValidator<DuplicatedUsername, User> {

  @Autowired
  private UserRepository userRepository;

  @Override
  public boolean isValid(User user, ConstraintValidatorContext context) {
    if (user.getId() != null) {
      return checkForDuplicatedUsername(user.getUsername(), user.getId());
    } else {
      return checkForDuplicatedUsername(user.getUsername());
    }
  }

  private boolean checkForDuplicatedUsername(String username) {
    List<User> users = userRepository.findByUsername(username);
    return users.isEmpty();
  }

  private boolean checkForDuplicatedUsername(String username, UUID id) {
    List<User> users = userRepository.findByUsernameAndIdNot(username, id);
    return users.isEmpty();
  }
}

测试文件

    private static Validator validator;

    @BeforeClass
    public static void setUpValidator() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    }

    @MockBean
    private UserRepository repository; 

    public void givenDuplicatedSettings_whenValidate_thenReturnInvalid() {
        String username = "duplicated";

        User user = new User(username);

        when(repository.findByUsername(username))
                .thenReturn(new User(username));

        // What I have
        Set<ConstraintViolation<User>> constraintViolations = validator.validate(user);
        assertEquals(1, constraintViolations.size());

        // What I would like to have
        // validator here must be DuplicatedUsernameValidator
        assertFalse(validator.isValid(user));
    }

主要原因是用户可能有其他验证器,我不想将它们作为一个单元进行验证。

这是第一个问题...第二个问题与 spring 上下文有关(一如既往)...上面的代码,即使使用Set&lt;ConstraintViolation&lt;User&gt;&gt; constraintViolations = validator.validate(user); 也不起作用,因为验证器代码中的存储库为空。即使当我运行它时,它也没有被正确注入,它按预期工作。我一直在使用以下注解@RunWith(SpringRunner.class)@DataJpaTest,以及以下代码:

    @Primary
    @Bean
    public Validator validator(){
        return new LocalValidatorFactoryBean();
    }

它们都没有按预期工作。我在哪一块拼图中做错了?

提前致谢!

【问题讨论】:

    标签: java spring unit-testing junit


    【解决方案1】:

    我认为您可以为此目的使用@TestConfiguration。例如,让您的课程以原始帖子状态呈现,并将这些行添加到测试课程:

    @TestConfiguration
    static class DuplicatedUsernameValidatorTestContextConfiguration {
    
        //for 'validator.validate(user);' usecase
        @Bean
        public Validator employeeService() {
           return new LocalValidatorFactoryBean();
        }
    
        //for 'validator.isValid(user,null)' usecase
        @Bean
        public DuplicateEmployeeValidator employeeService() {
           return new DuplicateEmployeeValidator();
        }
    }
    
    @Autowired
    private DuplicateEmployeeValidator dupValidator;
    
    @Autowired
    private Validator validator;
    
    // private static Validator validator;
    
    // @BeforeClass
    // public static void setUpValidator() {
    //     ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    //     validator = factory.getValidator();
    // }
    
    @MockBean
    private UserRepository repository;
    

    无需添加@DataJpaTest,UserRepository 将自动注入验证器 bean。

    【讨论】:

    • 感谢您的见解。我会试试看。我发现另一种方法是使用@Import(DuplicateEmployeeValidator.class)。它仍然需要@DataJpaTest,但为了加载正确的上下文。
    【解决方案2】:

    我个人不是所有注释驱动的单元测试的忠实拥护者,我建议您简单地为您添加一个构造函数 DuplicatedUsernameValidator 类,这样您就可以在构造时设置 UserRepository

    public class DuplicatedUsernameValidator implements ConstraintValidator<DuplicatedUsername, User> {
    
      @Autowired
      private UserRepository userRepository;
    
      // NEW!
      public DuplicatedUsernameValidator(UserRepository userRepository) {
        this.userRepository = userRepository;
      }
    
      @Override
      public boolean isValid(User user, ConstraintValidatorContext context) {
        ...
      }
    }
    

    然后在您的测试中,我会考虑执行以下操作:

    private static DuplicatedUsernameValidator validator;
    
    @MockBean
    private UserRepository repository; 
    
    @BeforeClass
    public static void setUpValidator() {
        // We're not testing the factory here, I would NOT use it...
        // Perhaps the factory should have its own test...
        //ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = new DuplicatedUsernameValidator(repository);
    }
    
    public void givenDuplicatedSettings_whenValidate_thenReturnInvalid() {
        //I appreciate the use of the given_when_then method names.
    }
    

    上述代码sn-ps中隐藏了以下cmets:

    • 我建议不要在单元测试中使用Validation.buildDefaultValidatorFactory() 调用。测试,一般来说,应该只测试被测类,我觉得这其实是在测试两个东西。
    • 如果您需要查看Validation.buildDefaultValidatorFactory() 方法的工作情况,那么我建议您对该类/方法进行单独的测试。
    • 非常感谢使用GivenWhenThen 单元测试方法名称。

    【讨论】:

    • 是的,我也避免使用buildDefaultValidatorFactory。不仅因为它为单元测试添加了另一层(应该尽可能地隔离和“单元”),还因为验证器验证了整个User,因此将检查所有其他验证。我绝对不想那样。我正努力在 GivenWhenThen 上更加勤奋,这真的很有帮助。就像我在另一个答案中所说的那样,我可以通过添加 @Import(DuplicateEmployeeValidator.class) + @DataJpaTest 来解决这个问题
    【解决方案3】:

    我能够通过使用Import 注释来解决这个问题。其他答案也有效,所以这是一个偏好问题

    @RunWith(SpringRunner.class)
    @DataJpaTest
    @Import(DuplicatedUsernameValidator.class)
    public class DuplicatedUsernameValidatorTest {
    
        @Aurowired
        private DuplicatedUsernameValidator validator;
    
        @MockBean
        private UserRepository repository; 
    
        public void givenDuplicatedSettings_whenValidate_thenReturnInvalid() {
            String username = "duplicated";
    
            User user = new User(username);
    
            when(repository.findByUsername(username))
                    .thenReturn(new User(username));
    
            assertFalse(validator.isValid(user));
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2020-11-09
      • 1970-01-01
      • 2011-12-06
      • 2022-10-13
      • 1970-01-01
      • 1970-01-01
      • 2019-01-06
      • 1970-01-01
      相关资源
      最近更新 更多