我需要测试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
}
答案 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约束注释的持久字段或属性的实体执行验证。