[JAVA] Accelerate testing of Validators that require DI in Spring Boot

While searching for faster unit test execution time in the Spring Boot project, I found out how to speed up the test execution of custom validators that require DI, so I will introduce it.

Confirmation environment

background

There are times when you want to make a DB connection with Bean Validation, such as when checking the uniqueness of an input value, and I created a number of custom validators using DI as follows.

@Documented
@Constraint(validatedBy = { EmployeeCodeUniqueValidator.class })
@Target({ TYPE })
@Retention(RUNTIME)
public @interface EmployeeCodeUniqueConstraint {
    ...
}
public class EmployeeCodeUniqueValidator implements ConstraintValidator<EmployeeCodeUniqueConstraint, Object> {
    private static final String EMPLOYEE_ID = "id";
    private static final String EMPLOYEE_CODE = "employeeCode";

    private final EmployeeDao employeeDao;

    public EmployeeCodeUniqueValidator(final EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }

    @Override
    public boolean isValid(final Object value, final ConstraintValidatorContext context) {
        String employeeCode = getField(value, EMPLOYEE_CODE);
        if (employeeCode == null) {
            return true;
        }
        Optional<Employee> employeeOpt = employeeDao.findByEmployeeCode(employeeCode);
        if (employeeOpt.isEmpty()) {
            return true;
        }
        ...

However, when writing a unit test for such a custom validator, you need to initialize the validator with the dependent components DI. So far, I've written tests using @ SpringBootTest, but Spring initialization takes a long time, and I can't stand the increase in execution time as the number of custom validators increases.

@SpringBootTest
class EmployeeCodeUniqueValidatorTest {

    @Autowired
    private Validator validator;

    @MockBean
    private EmployeeDao employeeDao;

    @EmployeeCodeUniqueConstraint
    class Form {
        public String id;
        public String employeeCode;
    }

    @Test
    void newEmployeeCode() {
        when(employeeDao.findByEmployeeCode("012345")).thenReturn(Optional.empty());
        Form form = new Form();
        form.employeeCode = "012345";
        assertTrue(validator.validate(form).isEmpty());
    }

    ...
}

solution

By specifying the classes attribute of the @SpringBootTest annotation, you can initialize only the required components and reduce the initialization time at test startup.

- @SpringBootTest
+ @SpringBootTest(classes = {ValidationAutoConfiguration.class, EmployeeDao.class})
class EmployeeCodeUniqueValidatorTest {

    @Autowired
    private Validator validator;

    @MockBean
    private EmployeeDao employeeDao;

    ...
}

Improvement effect

For reference only, it was applied to validator-related tests with less than 30 classes and less than 500 methods, and the execution time was reduced to less than half: +1:

Supplement

The test execution time will be faster, but it is a problem that you have to enumerate the classes to DI each time. While custom validator test classes have few DI components, it's pretty good, but for classes with a lot of dependencies, it may be better to prioritize maintainability over execution speed.

Also, if you are doing team development, it is difficult to unify such implementation rules, and test code that specifies only @SpringBootTest tends to increase without your knowledge. It seems good to use ArchUnit etc. to introduce a mechanism to mechanically notice.

@Test
void validatorTestShouldeRestrictAutowiredComponent() {
    classes().that().haveNameMatching(".+ValidatorTest$").and().areAnnotatedWith(SpringBootTest.class)
        .should()
        .beAnnotatedWith(new DescribedPredicate<>("@SpringBootTest(classes = {ValidationAutoConfiguration.class, ...}Limit the components to DI with") {
            @Override
            public boolean apply(JavaAnnotation annot) {
                if (!annot.getRawType().getSimpleName().equals("SpringBootTest")) {
                    return true;
                }
                JavaClass[] classes = (JavaClass[]) annot.get("classes").or(new JavaClass[]{});
                return Arrays.stream(classes)
                    .anyMatch(clazz -> clazz.getSimpleName().equals("ValidationAutoConfiguration"));
            }
        })
        .check(ALL_CLASSES);
}

Summary

In this article, I showed you how to speed up unit testing of custom validators in a project using Spring Boot. I've always known that custom validators are slow to run, but I haven't been able to find this method on the internet, so I hope it's helpful for people with the same problems.

Recommended Posts

Accelerate testing of Validators that require DI in Spring Boot
DI SessionScope Bean in Spring Boot 2 Filter
About DI of Spring ①
First Spring Boot (DI)
About DI of Spring ②
Specify the encoding of static resources in Spring Boot
I checked asynchronous execution of queries in Spring Boot 1.5.9
How to use CommandLineRunner in Spring Batch of Spring Boot
Specify spring.profiles.active via context-param of web.xml in Spring Boot
Set context-param in Spring Boot
Spring Boot 2 multi-project in Gradle
Introduction to Spring Boot ① ~ DI ~
Sample web application that handles multiple databases in Spring Boot 1.5
Major changes in Spring Boot 1.5
NoHttpResponseException in Spring Boot + WireMock
Introduction of library ff4j that realizes FeatureToggle with Spring Boot
Sample code that uses the Mustache template engine in Spring Boot
Get a proxy instance of the component itself in Spring Boot
Form that receives the value of the repeating item in Spring MVC
Unknown error in line 1 of pom.xml when using Spring Boot in Eclipse
Spring Boot Hello World in Eclipse
Spring Boot application development in Eclipse
Memorandum of understanding when Spring Boot 1.5.10 → Spring Boot 2.0.0
Write test code in Spring Boot
Going out of message (Spring boot)
Implement REST API in Spring Boot
What is @Autowired in Spring boot?
[Spring Boot] Role of each class
Implement Spring Boot application in Gradle
A memo that touched Spring Boot
Thymeleaf usage notes in Spring Boot
[Spring Boot] List of validation rules that can be used in the property file for error messages
A story about a Spring Boot project written in Java that supports Kotlin
Resolve null pointers for various sessions during MVC testing of Spring boot.
Autowired fields in a class that inherits TextWebSocketHandler in Spring Boot become NULL
Personal memo Features of Spring Boot (mainly from a DI point of view)
My memorandum that I want to make ValidationMessages.properties UTF8 in Spring Boot
Launch (old) Spring Boot project in IntelliJ
Build Spring Boot + Docker image in Gradle
Static file access priority in Spring boot
Output Spring Boot log in json format
Local file download memorandum in Spring Boot
Create Java Spring Boot project in IntelliJ
Loosen Thymeleaf syntax checking in Spring Boot
[Practice! ] Display Hello World in Spring Boot
WebMvcConfigurer Memorandum of Understanding for Spring Boot 2.0 (Spring 5)
Use DynamoDB query method in Spring Boot
Change session timeout time in Spring Boot
Introducing the Spring Boot Actuator, a function that makes the operation of Spring Boot applications easier.
Organize the differences in behavior of @NotBlank, @NotEmpty, @NotNull with Spring Boot + Thymeleaf
Get the path defined in Controller class of Spring boot as a list
How to set environment variables in the properties file of Spring boot application
I want to get the information of the class that inherits the UserDetails class of the user who is logged in with Spring Boot.