这是我使用带有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 方法作为夹具源:
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>
在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);
}
这是完整的测试代码:
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);
}
}