【问题标题】:How to validate Data in Service Layer?如何验证服务层中的数据?
【发布时间】:2022-01-17 19:05:48
【问题描述】:

我遇到了一个我无法解决的问题。 我目前正在使用 Spring Boot 构建一个 Rest Api,并且我想在服务层内验证我的用户实体。我尝试了不同的方法,但目前没有任何结果。在我的测试中,当它到达 create 方法时没有抛出异常。

这是我当前的代码:

用户实体

    @Entity
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank
    @Email(message = "User needs a valid Mail.")
    @Column(unique = true, nullable = false)
    private String mail;

    @NotBlank
    @Column(nullable = false)
    private String lastName;

    @NotBlank
    @Column(nullable = false)
    private String firstName;
    }

用户服务

    @Service
    @RequiredArgsConstructor
    public class UserService implements IUserService {

    private final UserRepository userRepository;

    @Override
    public User create(@Valid User user) {
        user.setId(null);
        User createdUser = userRepository.save(user);

        return createdUser;
    }

我的测试

    @SpringBootTest
    class UserServiceTest {

    @Mock
    UserRepository userRepository;

    @InjectMocks
    UserService userService;

    @Test
    void createUserFailsBecauseNoFirstName() {
        User testUser = new User("test@mail.de", "LastName", "FirstName");
        when(userRepository.save(any(User.class))).thenReturn(testUser);
        testUser.setFirstName(null);
        Assertions.assertThrows(ConstraintViolationException.class,
                () -> userService.create(testUser));
    }

pom.xml

    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.rest</groupId>
    <artifactId>test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.9</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

我尝试了什么:

  • @Validated 用于服务类(tutorial 我关注了)
  • @Validated 额外在方法级别
  • 删除 @Validated 和 @Validate 并改用 Validator,但它不会被注入(遵循 tutorial

【问题讨论】:

    标签: spring-boot rest validation hibernate-validator spring-boot-test


    【解决方案1】:

    因为您使用 @InjectMocks 来获取 UserService ,所以它不是由 Spring 管理的 bean,因为 @InjectMocks 是 Mockito 注释并且对 Spring 一无所知并且不知道如何从中获取 bean弹簧容器。因此,您现在实际上是在测试非弹簧 UserService bean,因此不会进行验证。

    更改为使用 @Autowired 从 Spring 容器中获取 UserService bean,并使用 @MockBean 将 Spring 容器中的 UserRepository bean 替换为模拟版本。您还需要在UserService 上添加@Validated

    @Service
    @Validated
    public class UserService implements IUserService {
    
    
    }
    
    @SpringBootTest
    class UserServiceTest {
    
        @MockBean
        UserRepository userRepository;
    
        @Autowired
        UserService userService;
    
        @Test
        void createUserFailsBecauseNoFirstName() {
            User testUser = new User("test@mail.de", "LastName", "FirstName");
            when(userRepository.save(any(User.class))).thenReturn(testUser);
            testUser.setFirstName(null);
            Assertions.assertThrows(ConstraintViolationException.class,
                    () -> userService.create(testUser));
        }
    }
    

    【讨论】:

    • 使用 Autowired 我得到了一个 ConstraintDeclarationException 所以我将 userService 放入 BeforeEach 并将模拟的存储库放入其中解决了它。正如你提到的,我还向 Service 类添加了 Validated 但我的测试仍然失败,说没有抛出异常。
    【解决方案2】:

    不是我正在寻找的真正解决方案,但即使在新的 Spring Boot 项目中,我也无法让它与 Annotations 一起使用。所以我现在在我的服务中使用了一个验证器,让它验证我的用户。

    服务

    @Service
    @RequiredArgsConstructor
    public class UserService implements IUserService {
    
    private final Validator validator;
    private final UserRepository userRepository;
    
    @Override
    public User create(User user) {
        user.setId(null);
    
        // new Validation
        Set<ConstraintViolation<T>> violations = validator.validate(objectToValidate);
        if (!violations.isEmpty()) {
            StringBuilder sb = new StringBuilder();
            for (ConstraintViolation<T> constraintViolation : violations) {
                sb.append(constraintViolation.getMessage());
            }
            throw new ResourceValidationException("Error occurred: " + sb.toString());
        }
    
    
        User createdUser = userRepository.save(user);
    
        return createdUser;
    }
    

    测试

    @SpringBootTest
    class UserServiceTest {
    
    @Mock
    UserRepository userRepository;
    
    @Autowired
    Validator validator;
    
    UserService userService = new UserService(validator, userRepository);
    
    
    @Test
    void createUserFailsBecauseNoFirstName() {
        User testUser = new User("test@mail.de", "LastName", "FirstName");
        when(userRepository.save(any(User.class))).thenReturn(testUser);
        testUser.setFirstName(null);
        Assertions.assertThrows(ConstraintViolationException.class,
                () -> userService.create(testUser));
    }
    

    因为我总是得到一个没有验证的验证器 bean(所以我的测试没有抛出预期的错误)并且我不想创建一个 ValidatorFactory 我还添加了一个包含 ValidatorBean 的配置。

    配置

    @Configuration
    public class AppConfig {
    
    @Bean
    public Validator defaultValidator() {
    
        return new LocalValidatorFactoryBean();
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-01-19
      • 2012-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-21
      • 1970-01-01
      相关资源
      最近更新 更多