Home > Software engineering >  How to validate Data in Service Layer?
How to validate Data in Service Layer?

Time:01-20

I've got a problem which I haven't been able to solve. I am currently building a Rest Api with Spring Boot and I want to validate my User Entity inside of the service layer. I have tried different approaches, but nothing worked out for now. In my Test no Exception gets thrown when it gets to the create method.

Here is my current code:

User Entity

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

User Service

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

My Test

    @SpringBootTest
    class UserServiceTest {

    @Mock
    UserRepository userRepository;

    @InjectMocks
    UserService userService;

    @Test
    void createUserFailsBecauseNoFirstName() {
        User testUser = new User("[email protected]", "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>

What I tried:

  • @Validated for the Service Class (tutorial I followed)
  • @Validated additionally at Method Level
  • removing @Validated & @Validate and use Validator instead, but it doesn't get injected (followed that tutorial)

CodePudding user response:

Because you are using @InjectMocks to get the UserService , it is not a bean that is managed by Spring since @InjectMocks is a Mockito annotation and know nothing about Spring and does not know how to get a bean from Spring container. So you are actually testing a non-spring UserService bean now and hence the validation does not takes place.

Change to use @Autowired to get the UserService bean from Spring container , and use @MockBean to replace the UserRepository bean in the Spring container with a mocked version. Also you need to add @Validated on the UserService too :

@Service
@Validated
public class UserService implements IUserService {


}
@SpringBootTest
class UserServiceTest {

    @MockBean
    UserRepository userRepository;

    @Autowired
    UserService userService;

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

CodePudding user response:

Not really the solution that I was searching for, but I couldn't get it to work with Annotations even in a new Spring Boot Project. So I now used a Validator in my Service and let it validate my User.

Service

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

Test

@SpringBootTest
class UserServiceTest {

@Mock
UserRepository userRepository;

@Autowired
Validator validator;

UserService userService = new UserService(validator, userRepository);


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

And since I always got a validator bean which didn't validate (so my tests didn't throw the expected error) and I didn't want to create a ValidatorFactory I also added a Configuration which holds a ValidatorBean.

Config

@Configuration
public class AppConfig {

@Bean
public Validator defaultValidator() {

    return new LocalValidatorFactoryBean();
    }
}
  •  Tags:  
  • Related