如何使用JUnit测试类的validation注释?

我需要测试validation注释,但看起来它们不起作用。 我不确定JUnit是否也正确。 目前,测试将通过,但您可以看到指定的电子邮件地址是错误的。

JUnit的

public static void testContactSuccess() { Contact contact = new Contact(); contact.setEmail("Jackyahoo.com"); contact.setName("Jack"); System.err.println(contact); } 

要测试的类

 public class Contact { @NotNull @Size(min = 1, max = 10) String name; @NotNull @Pattern(regexp="[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\." +"[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@" +"(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", message="{invalid.email}") String email; @Digits(fraction = 0, integer = 10) @Size(min = 10, max = 10) String phone; getters and setters } 

另一个答案说“注释本身没有做任何事情,你需要使用Validator来处理对象”是正确的,但是,答案缺乏关于如何使用Validator实例来完成它的工作指令,对我来说这是我真正想要的是什么。

Hibernate-validator是这种validation器的参考实现。 您可以非常干净地使用它:

 import static org.junit.Assert.assertFalse; import java.util.Set; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class ContactValidationTest { private static Validator validator; @Before public void setUp() { ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); validator = factory.getValidator(); } @Test public void testContactSuccess() { // I'd name the test to something like // invalidEmailShouldFailValidation() Contact contact = new Contact(); contact.setEmail("Jackyahoo.com"); contact.setName("Jack"); Set> violations = validator.validate(contact); assertFalse(violations.isEmpty()); } } 

这假设您有validation器实现和junit作为依赖项。

使用Maven pom的依赖项示例:

  org.hibernate 5.2.4.Final hibernate-validator   junit junit 4.12 test  

注释本身不会执行任何操作,您需要使用Validator来处理对象。

您的测试需要运行这样的代码

  Configuration configuration = Validation .byDefaultProvider() .providerResolver( new MyResolverStrategy() ) <== this is where is gets tricky .configure(); ValidatorFactory factory = configuration.buildValidatorFactory(); Contact contact = new Contact(); contact.setEmail("Jackyahoo.com"); contact.setName("Jack"); factory.getValidator().validate(contact); <== this normally gets run in the background by whatever framework you are using 

但是,您在这里遇到的困难是这些都是接口,您需要实现才能进行测试。 您可以自己实现它或找到一个使用它。

但是你想问自己的问题是你想测试什么? That the hibernate validator works the way it should? 或者that your regex is correct?

如果这是我,我会假设Validator工作(即其他人测试过)并专注于正则表达式。 这将涉及一些反思

 public void emailRegex(String email,boolean validates){ Field field = Contact.class.getDeclaredField("email"); javax.validation.constraints.Pattern[] annotations = field.getAnnotationsByType(javax.validation.constraints.Pattern.class); assertEquals(email.matches(annotations[0].regexp()),validates); } 

然后你可以定义你的testMethods,它们是实际的unit testing

 @Test public void testInvalidEmail() throws NoSuchFieldException { emailRegex("Jackyahoo.com", false); } @Test public void testValidEmail() throws NoSuchFieldException { emailRegex("jack@yahoo.com", true); } @Test public void testNoUpperCase() throws NoSuchFieldException { emailRegex("Jack@yahoo.com", false); } 

您需要检查两件事:

validation规则配置正确

validation规则可以按照其他人的建议进行检查 – 通过创建validation器对象并手动调用它:

 Set violations = validator.validate(contact); assertFalse(violations.isEmpty()); 

有了这个,你应该检查所有可能的情况 – 可能有几十个(在这种情况下应该有几十个)。

validation由框架触发

在你的情况下,你用Hibernate检查它,因此应该有一个初始化它并触发一些Hibernate操作的测试。 请注意,为此,您只需要检查一个字段的一个失败规则 – 这就足够了。 您无需再次检查所有规则。 示例可以是:

 @Test(expected = ConstraintViolationException.class) public void validationIsInvokedBeforeSavingContact() { Contact contact = Contact.random(); contact.setEmail(invalidEmail()); contactsDao.save(contact) session.flush(); // or entityManager.flush(); } 

注意:别忘了触发flush() 。 如果您使用UUID或序列作为ID生成策略,那么当您save()时INSERT不会被刷新 – 它将被推迟到稍后。

这一切都是如何构建测试金字塔的一部分 – 您可以在此处找到更多详细信息 。

如:

 public class Test { @Autowired private Validator validator; public void testContactSuccess() { Contact contact = new Contact(); contact.setEmail("Jackyahoo.com"); contact.setName("Jack"); System.err.println(contact); Set> violations = validator.validate(contact); assertTrue(violations.isEmpty()); } } 

并且还需要在context.xml中添加autowired,例如:

   

在这里,我的方法是使用带有一些javax.validation.constraints约束的字段对我的对象进行unit testing。
我将举例说明Java 8,JPA实体,Spring Boot和JUnit 5,但无论上下文和框架如何,总体思路都是一样的:
我们有一个名义上的场景,其中所有字段都被正确评估,并且通常是多个错误场景,其中一个或多个字段未正确估值。

测试现场validation并不是特别困难。
但是由于我们有许多要validation的领域,测试可能会变得更加复杂,我们可以忘记一些情况,在两个案例之间的测试中引入副作用来validation或简单地引入重复。
我会考虑如何避免这种情况。

在OP代码中,我们假设3个字段具有NotNull约束。 我认为在3个不同的约束条件下,模式及其值不太明显。

我首先写了一个名义场景的unit testing:

 import org.junit.jupiter.api.Test; @Test public void persist() throws Exception { Contact contact = createValidContact(); // action contactRepository.save(contact); entityManager.flush(); entityManager.clear(); // assertion on the id for example ... } 

我提取代码以创建一个方法的有效联系人,因为它将有助于没有名义上的情况:

 private Contact createValidContact(){ Contact contact = new Contact(); contact.setEmail("Jackyahoo.com"); contact.setName("Jack"); contact.setPhone("33999999"); return contact; } 

现在我用@MethodSource方法编写@parameterizedTest作为fixture源:

 import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import javax.validation.ConstraintViolationException; @ParameterizedTest @MethodSource("persist_fails_with_constraintViolation_fixture") void persist_fails_with_constraintViolation(Contact contact ) { assertThrows(ConstraintViolationException.class, () -> { contactRepository.save(contact); entityManager.flush(); }); } 

要编译/运行@parameterizedTest ,请考虑添加junit-jupiter-api依赖项中未包含的所需依赖项:

  org.junit.jupiter junit-jupiter-params ${junit-jupiter.version} test  

在夹具方法中创建无效的联系人,这个想法很简单。 对于每种情况,我创建一个新的有效联系对象,我只错误地设置字段以validation相关的。
通过这种方式,我确保不存在案例之间的副作用,并且每个案例都会引发预期的validationexception,因为如果没有字段集,则有效的联系人会成功保留。

 private static Stream persist_fails_with_constraintViolation_fixture() { Contact contactWithNullName = createValidContact(); contactWithNullName.setName(null); Contact contactWithNullEmail = createValidContact(); contactWithNullEmail.setEmail(null); Contact contactWithNullPhone = createValidContact(); contactWithNullPhone.setPhone(null); return Stream.of(contactWithNullName, contactWithNullEmail, contactWithNullPhone); } 

这是完整的测试代码:

 import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import javax.validation.ConstraintViolationException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; import org.springframework.test.context.junit.jupiter.SpringExtension; @DataJpaTest @ExtendWith(SpringExtension.class) public class ContactRepositoryTest { @Autowired private TestEntityManager entityManager; @Autowired private ContactRepository contactRepository; @BeforeEach public void setup() { entityManager.clear(); } @Test public void persist() throws Exception { Contact contact = createValidContact(); // action contactRepository.save(contact); entityManager.flush(); entityManager.clear(); // assertion on the id for example ... } @ParameterizedTest @MethodSource("persist_fails_with_constraintViolation_fixture") void persist_fails_with_constraintViolation(Contact contact ) { assertThrows(ConstraintViolationException.class, () -> { contactRepository.save(contact); entityManager.flush(); }); } private static Stream persist_fails_with_constraintViolation_fixture() { Contact contactWithNullName = createValidContact(); contactWithNullName.setName(null); Contact contactWithNullEmail = createValidContact(); contactWithNullEmail.setEmail(null); Contact contactWithNullPhone = createValidContact(); contactWithNullPhone.setPhone(null); return Stream.of(contactWithNullName, contactWithNullEmail, contactWithNullPhone); } } 

我认为validation在调用预定义方法之后会起作用,这些方法通常由容器完成,而不是在调用对象的setter之后立即执行。 从您分享的文档链接:

>默认情况下,持久性提供程序将在PrePersist,PreUpdate和PreRemove生命周期事件之后立即自动对具有使用Bean Validation约束注释的持久字段或属性的实体执行validation。