前面记录的第一种方式与其说是单测更像是集成测试,将方法的入口参数和方法里面的业务逻辑一起给测试了,写法也稍微复杂些,更重要的是多个测试案例一起跑可能会相互有影响。
service 层的测试可以将 方法入口参数 和 方法中的业务逻辑分开做单测,因为最终的目的是让整个项目有一个较为全面的单测覆盖,以便日后可以很有底气的做重构。
service 层的方法:
public void createNewUser(CreateUserCommand createUserCommand);
方法入口参数长这个样子,要校验 name 是不是已经存在。
public class CreateUserCommand {
@NotBlank
@UserNameExistConstraint // name不能重复
private String name;
@Min(value = 1)
private int age;
public CreateUserCommand(String name, int age) {
this.name = name;
this.age = age;
}
}
// ---
@Documented
@Constraint(validatedBy = UserNameExistValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserNameExistConstraint {
String message() default "{user.name.exist}";
Class[] groups() default {};
Class[] payload() default {};
}
// ---
@Component
public class UserNameExistValidator
implements ConstraintValidator<UserNameExistConstraint, String> {
@Autowired private UserRepository userRepository;
@Override
public boolean isValid(String name, ConstraintValidatorContext context) {
// 从数据库中查下 name 是不是已经有了。
if (userRepository.ofId(name).isPresent()) {
return false;
}
return true;
}
}
这里为 createUserCommand 单独写单测。
@RunWith(SpringRunner.class)
@Import(LocalValidatorFactoryBean.class)
public class ResourceDeletedValidatorTest {
@MockBean private UserRepository userRepository;
@Autowired private Validator validator;
@Test
public void should_throw_exception_when_name_is_existed() {
String name = "new-name";
User user = Mockito.mock(User.class);
when(userRepository.ofId(eq(name))).thenReturn(Optional.of(user));
Set<ConstraintViolation<CreateUserCommand>> constraintViolations =
validator.validate(new CreateUserCommand(name,"20"));
assertTrue(constraintViolations.size() == 1);
}
}
这种方式让单测变的更单一些,而且写法也简单。