前面记录的第一种方式与其说是单测更像是集成测试,将方法的入口参数和方法里面的业务逻辑一起给测试了,写法也稍微复杂些,更重要的是多个测试案例一起跑可能会相互有影响。

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

这种方式让单测变的更单一些,而且写法也简单。

更多推荐