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