Java 如何使用 JUnit 测试类的验证注释?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29069956/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to test validation annotations of a class using JUnit?
提问by Hyman
I need to test the validation annotationsbut it looks like they do not work. I am not sure if the JUnit is also correct. Currently, the test will be passed but as you can see the specified email address is wrong.
我需要测试验证注释,但看起来它们不起作用。我不确定 JUnit 是否也正确。目前,测试将通过,但您可以看到指定的电子邮件地址是错误的。
JUnit
JUnit
public static void testContactSuccess() {
Contact contact = new Contact();
contact.setEmail("Hymanyahoo.com");
contact.setName("Hyman");
System.err.println(contact);
}
Class to be tested
要测试的类
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
}
回答by vikas
I think validations would work after calling predefined methods which is usually done by the containers mostly not immediately after calling setters of the object. From the documentation link you shared:
我认为在调用预定义的方法后验证会起作用,这通常由容器完成,而不是在调用对象的 setter 后立即完成。从您共享的文档链接:
> By default, the Persistence provider will automatically perform validation on entities with persistent fields or properties annotated with Bean Validation constraints immediately after the PrePersist, PreUpdate, and PreRemove lifecycle events.
> 默认情况下,持久化提供程序将在 PrePersist、PreUpdate 和 PreRemove 生命周期事件发生后立即自动对具有用 Bean 验证约束注释的持久字段或属性的实体执行验证。
回答by BevynQ
The annotations do not do anything by themselves, you need to use a Validator to process the object.
注释本身不做任何事情,您需要使用 Validator 来处理对象。
Your test needs to run some code like this
你的测试需要运行一些这样的代码
Configuration<?> configuration = Validation
.byDefaultProvider()
.providerResolver( new MyResolverStrategy() ) <== this is where is gets tricky
.configure();
ValidatorFactory factory = configuration.buildValidatorFactory();
Contact contact = new Contact();
contact.setEmail("Hymanyahoo.com");
contact.setName("Hyman");
factory.getValidator().validate(contact); <== this normally gets run in the background by whatever framework you are using
However the difficulty you face here are these are all interfaces, you will need implementations to be able to test. You could implement it yourself or find one to use.
然而,您在这里面临的困难是这些都是接口,您需要实现才能进行测试。您可以自己实现它或找到一个使用它。
However the question you want to ask yourself is what are you trying to test? That the hibernate validator works the way it should?
or that your regex is correct?
但是,您要问自己的问题是您要测试什么?That the hibernate validator works the way it should?
或者that your regex is correct?
If this was me I would assume that the Validator works(ie someone else tested that) and focus on the regex. Which would involve a bit of reflection
如果这是我,我会假设验证器有效(即其他人测试过)并专注于正则表达式。这将涉及一些反思
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);
}
then you can define your testMethods which are actual unit tests
然后你可以定义你的 testMethods,它们是实际的单元测试
@Test
public void testInvalidEmail() throws NoSuchFieldException {
emailRegex("Hymanyahoo.com", false);
}
@Test
public void testValidEmail() throws NoSuchFieldException {
emailRegex("[email protected]", true);
}
@Test
public void testNoUpperCase() throws NoSuchFieldException {
emailRegex("[email protected]", false);
}
回答by chengpohi
such as:
如:
public class Test {
@Autowired
private Validator validator;
public void testContactSuccess() {
Contact contact = new Contact();
contact.setEmail("Hymanyahoo.com");
contact.setName("Hyman");
System.err.println(contact);
Set<ConstraintViolation<Contact>> violations = validator.validate(contact);
assertTrue(violations.isEmpty());
}
}
and you also need add bean autowired in your context.xml, such as:
并且您还需要在 context.xml 中添加 bean autowired,例如:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
</bean>
回答by eis
The other answersaying that "the annotations do not do anything by themselves, you need to use a Validator to process the object" is correct, however, the answer lacks working instructions on how to do it using a Validator instance, which for me was what I really wanted.
在对方回答说,“注释不自己做任何事情,你需要使用一个验证器来处理对象”是正确的,然而,答案没有对如何使用验证器实例,这对我来说是这样做的工作指令我真正想要的。
Hibernate-validator is the reference implementation of such a validator. You can use it quite cleanly like this:
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("Hymanyahoo.com");
contact.setName("Hyman");
Set<ConstraintViolation<Contact>> violations = validator.validate(contact);
assertFalse(violations.isEmpty());
}
}
This assumes you have validator implementation and junit as dependencies.
这假设您有验证器实现和 junit 作为依赖项。
Example of dependencies using Maven pom:
使用 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>
回答by Stanislav Bashkyrtsev
There are 2 things that you need to check:
您需要检查两件事:
The validation rules are configured correctly
验证规则配置正确
The validation rules can be checked the way others advise - by creating a validator object and invoking it manually:
可以按照其他人建议的方式检查验证规则 - 通过创建验证器对象并手动调用它:
Set violations = validator.validate(contact);
assertFalse(violations.isEmpty());
With this you should check all the possible cases - there could be dozens of them (and in this case there shouldbe dozens of them).
有了这个,你应该检查所有可能的情况 - 可能有几十个(在这种情况下应该有几十个)。
The validation is triggered by the frameworks
验证由框架触发
In your case you check it with Hibernate, therefore there should be a test that initializes it and triggers some Hibernate operations. Note that for this you need to check only one failing rule for one single field- this will be enough. You don't need to check all the rules from again. Example could be:
在您的情况下,您使用 Hibernate 检查它,因此应该有一个测试来初始化它并触发一些 Hibernate 操作。请注意,为此您只需要检查一个字段的一个失败规则- 这就足够了。您无需再次检查所有规则。示例可能是:
@Test(expected = ConstraintViolationException.class)
public void validationIsInvokedBeforeSavingContact() {
Contact contact = Contact.random();
contact.setEmail(invalidEmail());
contactsDao.save(contact)
session.flush(); // or entityManager.flush();
}
NB: don't forget to trigger flush()
. If you work with UUIDs or sequences as an ID generation strategy, then INSERT is not going to be flushed when you save()
- it's going to be postponed until later.
注意:不要忘记触发flush()
. 如果您使用 UUID 或序列作为 ID 生成策略,那么 INSERT 不会在您刷新时刷新save()
- 它会被推迟到以后。
This all is a part of how to build a Test Pyramid - you can find more details here.
回答by davidxxx
Here my way to unit test my objects with fields annotated with some javax.validation.constraints
constraints.
I will give an example with Java 8, JPA entity, Spring Boot and JUnit 5 but the overall idea is the same whatever the context and the frameworks :
We have a nominal scenario where all fields are correctly valued and generally multiple error scenarios where one or more fields are not correctly valued.
这是我使用带有一些javax.validation.constraints
约束注释的字段对我的对象进行单元测试的方法。
我将举一个 Java 8、JPA 实体、Spring Boot 和 JUnit 5 的示例,但无论上下文和框架如何,总体思路都是相同的:
我们有一个名义场景,其中所有字段都被正确赋值,并且通常存在多个错误场景,其中一个或更多的字段没有正确估值。
Testing field validation is not a particularly hard thing.
But as we have many fields to validate, the tests may become more complex, we can forget some cases, introducing side effects in tests between two cases to validate or simply introduce duplication.
I will give my mind about how to avoid that.
测试现场验证并不是一件特别困难的事情。
但是由于我们有很多字段要验证,测试可能会变得更加复杂,我们可以忘记一些案例,在两个案例之间的测试中引入副作用来验证或简单地引入重复。
我会考虑如何避免这种情况。
In the OP code, we will suppose that the 3 fields have a NotNull
constraint. I think that under 3 distinct constraints, the pattern and its value are less visible.
在 OP 代码中,我们假设这 3 个字段有一个NotNull
约束。我认为在 3 个不同的约束下,模式及其价值不太明显。
I wrote first a unit test for the nominal scenario :
我首先为名义场景编写了一个单元测试:
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
...
}
I extract the code to create a valid contact into a method as it will be helpful for no nominal cases :
我提取了代码以在方法中创建有效的联系人,因为它对没有名义的情况很有帮助:
private Contact createValidContact(){
Contact contact = new Contact();
contact.setEmail("Hymanyahoo.com");
contact.setName("Hyman");
contact.setPhone("33999999");
return contact;
}
Now I write a @parameterizedTest
with as fixture source a @MethodSource
method :
现在我@parameterizedTest
用夹具源编写一个@MethodSource
方法:
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();
});
}
To compile/run @parameterizedTest
, think of adding the required dependency that is not included in the junit-jupiter-api dependency :
要编译/运行@parameterizedTest
,请考虑添加未包含在 junit-jupiter-api 依赖项中的所需依赖项:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
In the fixture method to create invalid contacts, the idea is simple. For each case, I create a new valid contact object and I set incorrectly only the field to validate concerned to.
In this way, I ensure that no side effect between cases are present and that each case provokes itself the expected validation exception as without the field set the valid contact was successful persisted.
在创建无效触点的fixture方法中,思路很简单。对于每种情况,我都创建了一个新的有效联系人对象,并且只错误地设置了要验证的字段。
通过这种方式,我确保案例之间不存在副作用,并且每个案例都会引发预期的验证异常,因为如果没有设置字段,则有效联系人成功保留。
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);
}
Here is the full test code :
这是完整的测试代码:
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);
}
}
回答by LazR
First thanks @Eis for the answer, it helped me. It's a good way to fail the test, but I wanted a bit more "life-like" behaviour. At runtime an exception would be thrown so I came up with this:
首先感谢@Eis 的回答,它帮助了我。这是使测试失败的好方法,但我想要更“逼真”的行为。在运行时会抛出异常,所以我想出了这个:
/**
* 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());
}
}
Have an entity with validation:
有一个带有验证的实体:
@Data
public class MyEntity {
@NotBlank(message = "Name cannot be empty!")
private String name;
}
In a test you can pass an instance with invalid attributes and expect an exception:
在测试中,您可以传递具有无效属性的实例并期望出现异常:
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(""));
}
回答by AdilOoze
I found a simple way to test validation annotations using javax, it uses less lines than the top answer:
我找到了一种使用 javax 测试验证注释的简单方法,它使用的行数比最佳答案少:
Declare the Validator at Class level:
在类级别声明验证器:
private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Then in your test simply call it on the object you require validation on, with what exception you are validating:
然后在您的测试中,只需在您需要验证的对象上调用它,您正在验证什么异常:
Set<TheViolation<TheClassYouAreValidating> violations = validator.validate(theInstanceOfTheClassYouAreValidating);
Then simply assert the number of expected violations:
然后简单地断言预期的违规次数:
assertThat(violations.size()).isEqualTo(1);
You will need to add this to your dependencies (gradle):
您需要将此添加到您的依赖项(gradle):
compile group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final'