Search code examples
javaspring-bootunit-testingspring-validatorspring-validation

unit test @Valid of springboot without having to spin up / mock the server


I would like to write some unit test to test the @Valid of SpringBoot request, hopefully, without having to spin the server.

For instance, in the "old way", one could write a piece of code like this:

record SomeRequest(String clientId) { }
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
class FieldValidationController {

    @PostMapping("/validate")
    String question(@RequestBody SomeRequest someRequest) {
        validateRequestOldFashionWay(someRequest);
        return "please validate the field";
    }

    //Not using @Valid here
    public void validateRequestOldFashionWay(SomeRequest someRequest) {
        if (someRequest.clientId() == null || someRequest.clientId().isEmpty()) {
            throw new IllegalArgumentException("clientId cannot be null or empty");
        }
    }

}
    @Test
    public void clientIdEmptyTestWithoutHavingToStartSpring() throws Exception {
        FieldValidationController controller = new FieldValidationController();
        SomeRequest someRequest1 = new SomeRequest("");
        assertThrows(IllegalArgumentException.class, () -> controller.validateRequestOldFashionWay(someRequest1));
        SomeRequest someRequest3 = new SomeRequest("good");
        controller.validateRequestOldFashionWay(someRequest3);
    }

As you can see above, it is a straightforward test on the validation method.

There is no need to spin up the server, bring in mockmvc, etc

Now, moving the code to Spring validation:

import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;

record SomeRequest(@NotNull @NotEmpty String clientId) { }
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
class FieldValidationController {

    @PostMapping("/validate")
    String question(@Valid @RequestBody SomeRequest someRequest) {
        return "please validate the field";
    }

}

I understand I can write a unit test which looks like this to test the validation feature.

@WebMvcTest(FieldValidationController.class)
class FieldValidationControllerTest {

    @Autowired
    private MockMvc mvc;

        @Test
    public void clientIdNotNullTest() throws Exception {
        SomeRequest someRequest = new SomeRequest("notNullTest");
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, false);
        ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter();
        String requestJson=ow.writeValueAsString(someRequest);
        mvc.perform(post("/validate").contentType(APPLICATION_JSON_UTF8)
                        .content(requestJson))
                .andExpect(status().isOk());
    }

But this would need to spin up spring, it looks more like an integration test now.

To keep the question simple, how can I test the same functionality, without having to spin up spring?


Solution

  • I think you are looking for this. First my DTO:

    import jakarta.validation.constraints.NotBlank;
    import lombok.AllArgsConstructor;
    import lombok.NoArgsConstructor;
    
    @NoArgsConstructor
    @AllArgsConstructor
    public class TestDto {
    
        @NotBlank
        private String name;
    
    }
    

    and here is my test:

    import jakarta.validation.ConstraintViolation;
    import jakarta.validation.Validator;
    import jakarta.validation.ValidatorFactory;
    import jakarta.validation.Validation;
    import org.junit.jupiter.api.Assertions;
    import org.junit.jupiter.api.Test;
    
    import java.util.Set;
    
    class ValidatorUnitTest {
    
        @Test
        void test() {
            // prepare
            ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
            Validator validator = factory.getValidator();
    
            // act
            Set<ConstraintViolation<Object>> validate1 = validator.validate(new TestDto("Patrick"));
            Set<ConstraintViolation<Object>> validate2 = validator.validate(new TestDto());
    
            // assert
            Assertions.assertEquals(0, validate1.size());
            Assertions.assertEquals(1, validate2.size());
        }
    }