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

时间:2015-03-16 05:09:40

标签: java junit

我需要测试validation annotations,但看起来它们不起作用。我不确定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

}

9 个答案:

答案 0 :(得分:51)

other answer说“注释本身不做任何事情,你需要使用Validator来处理对象”是正确的,但是,答案缺乏关于如何使用Validator执行此操作的工作指令例如,对我而言,这就是我真正想要的。

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

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 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<ConstraintViolation<Contact>> violations = validator.validate(contact);
        assertFalse(violations.isEmpty());
    }
}

这假设你有验证器实现和junit作为依赖。

使用Maven pom的依赖关系示例:

<dependency>
    <groupId>org.hibernate</groupId>
    <version>5.2.4.Final</version>
    <artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

答案 1 :(得分:8)

我找到了一种使用javax测试验证批注的简单方法:

在课程级别声明Validator

private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

然后在测试中,只需在您需要验证的object上用您正在验证的exception对其进行调用:

Set<TheViolation<TheClassYouAreValidating> violations = validator.validate(theInstanceOfTheClassYouAreValidating);

然后简单地assert违反预期数量:

assertThat(violations.size()).isEqualTo(1);

您需要将其添加到依赖项(gradle):

compile group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final'

答案 2 :(得分:4)

注释本身不会执行任何操作,您需要使用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,它们是实际的单元测试

@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);
}

答案 3 :(得分:1)

您需要检查两件事:

验证规则配置正确

可以像其他人建议的那样检查验证规则 - 通过创建验证器对象并手动调用它:

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

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

验证由框架

触发

在你的情况下你用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不会被刷新 - 它将被推迟到稍后。

这一切都是如何构建测试金字塔的一部分 - 你可以找到more details here

答案 4 :(得分:1)

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

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

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

我首先写了名义情景的单元测试:

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;     
}

现在我用@parameterizedTest方法编写一个@MethodSource作为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依赖项中未包含的必需依赖项:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-params</artifactId>
    <version>${junit-jupiter.version}</version>
    <scope>test</scope>
</dependency>

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

private static Stream<Contact> 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<Contact> 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);
    }
}

答案 5 :(得分:1)

首先感谢@Eis for the answer,它对我有所帮助。这是通过测试的好方法,但是我想要更多“栩栩如生”的行为。在运行时会引发异常,所以我想到了这个:

/**
 * Simulates the behaviour of bean-validation e.g. @NotNull
 */
private void validateBean(Object bean) throws AssertionError {
    Optional<ConstraintViolation<Object>> violation = validator.validate(bean).stream().findFirst();
    if (violation.isPresent()) {
        throw new ValidationException(violation.get().getMessage());
    }
}

拥有一个具有验证的实体:

@Data
public class MyEntity {

@NotBlank(message = "Name cannot be empty!")
private String name;

}

在测试中,您可以传递具有无效属性的实例并期望出现异常:

private Validator validator;

@Before
public void setUp() {
    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    validator = factory.getValidator();
}

@Test(expected = ValidationException.class)
public void testValidationWhenNoNameThenThrowException() {
    validateBean(new Entity.setName(""));
}

答案 6 :(得分:0)

如:

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<ConstraintViolation<Contact>> violations = validator.validate(contact);
        assertTrue(violations.isEmpty());
    }
}

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

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
</bean>

答案 7 :(得分:0)

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;

public class ValidationTest {

    private Validator validator;

    @Before
    public void init() {

        ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
        this.validator = vf.getValidator();

    }

    @Test
    public void prereqsMet() {
        Workshop validWorkshop = new Workshop(2, 2, true, 3);
        Set<ConstraintViolation<Workshop>> violations = this.validator.validate(validWorkshop);
        assertTrue(violations.isEmpty());
    }  
}

严格来说,它不是单元测试,而是集成测试。在单元测试中,您只想测试验证器逻辑,而与SPI没有任何依赖关系。

https://www.adam-bien.com/roller/abien/entry/unit_integration_testing_the_bean

答案 8 :(得分:-1)

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

  

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