Spring boot Spring Boot UnitTest:@ConstraintValidator中的值

Spring boot Spring Boot UnitTest:@ConstraintValidator中的值,spring-boot,spring-boot-test,spring-framework-beans,javax.validation,Spring Boot,Spring Boot Test,Spring Framework Beans,Javax.validation,我目前正在提供覆盖率-通过MockMVC请求调用测试DTO的验证。 我最近在我的注册约束验证器中引入了一个新字段supportedSpecializations,我从application.properties中注入了该字段的值,以便于维护和扩展。请参阅下面的代码片段: @Component public class RegistrationValidator implements ConstraintValidator<Registration, String> { //

我目前正在提供覆盖率-通过MockMVC请求调用测试DTO的验证。 我最近在我的注册约束验证器中引入了一个新字段supportedSpecializations,我从application.properties中注入了该字段的值,以便于维护和扩展。请参阅下面的代码片段:

@Component
public class RegistrationValidator implements ConstraintValidator<Registration, String> {

    //campus.students.supportedspecializations="J2E,.NET,OracleDB,MySQL,Angular"

    @Value("${campus.students.supportedspecializations}")
    private String supportedSpecializations;

    private String specializationExceptionMessage;

    //All ExceptionMessages are maintained in a separate class
    @Override
    public void initialize(Registration constraintAnnotation) {
        exceptionMessage = constraintAnnotation.regionException().getMessage();
    }

    @Override
    public boolean isValid(RegistrationData regData, ConstraintValidatorContext context) {

        String[] specializations = supportedSpecializations.split(",");
        boolean isValidSpecialization = Arrays.stream(specializations)
                    .anyMatch(spec -> spec.equalsIgnoreCase(regData.getSpec()));
        if (!isValidSpecialization){
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(specializationExceptionMessage)
                        .addConstraintViolation();
            return false;
            }
        //additional validation logic...
        return true;
    }
}

我尝试用@RunWith(SpringRunner.class)@SpringBootTest(classes=Application.class)注释我的测试类,但由于@Value未解析,验证测试仍然失败。我可能错了,但我认为ConstraintValidator的实例是在我们到达restController之前创建的,因此MockMVCperform(…)调用不能简单地确保验证器中适当的@Value被注入到supportedSpecializations中。

是, 使用
ReflectionTestUtil

使用
ReflectionTestUtil.setField
在中设置
supportedSpecializations
的值
setup()
方法(junit<1.4) 或者在单元测试中的
@Before
注释方法(junit>1.4)中

更多详细信息
我建议不要在单元测试中使用
MockMVC
; 这对于集成测试来说很好, 只是不是单元测试

单元测试不需要启动弹簧; 您永远不需要Spring来执行单元测试的注入。 相反 实例化您正在测试的类并直接调用这些方法

下面是一个简单的例子:

公共类TestRegistrationValidator
{
私有静态最终字符串VALUE\u EXCEPTION\u MESSAGE=“VALUE\u EXCEPTION\u MESSAGE”;
私有静态最终字符串值\u支持的\u专门化=“BLAMMY,KAPOW”;
私有注册验证程序classToTest;
@嘲弄
私人注册;
@嘲弄
私有RegionExceptionType mockRegionExceptionType;//使用RegionException的实际类型。
@以前
公共无效预测试设置()
{
initMocks(this);
ReflectionTestUtils.setField(classToTest,“supportedSpecializations”,VALUE\u SUPPORTED\u SPECIALIZATIONS);
doReturn(值\异常\消息).when(mockRegionExceptionType).getMessage();
doReturn(mockRegionExceptionType).when(mockRegion.regionException();
}
@试验
公共无效初始化\u allGood\u success()
{
classToTest.initialize(模拟注册);
…断言一些东西。
…也许可以验证一些东西。
}
}
是, 使用
ReflectionTestUtil

使用
ReflectionTestUtil.setField
在中设置
supportedSpecializations
的值
setup()
方法(junit<1.4) 或者在单元测试中的
@Before
注释方法(junit>1.4)中

更多详细信息
我建议不要在单元测试中使用
MockMVC
; 这对于集成测试来说很好, 只是不是单元测试

单元测试不需要启动弹簧; 您永远不需要Spring来执行单元测试的注入。 相反 实例化您正在测试的类并直接调用这些方法

下面是一个简单的例子:

公共类TestRegistrationValidator
{
私有静态最终字符串VALUE\u EXCEPTION\u MESSAGE=“VALUE\u EXCEPTION\u MESSAGE”;
私有静态最终字符串值\u支持的\u专门化=“BLAMMY,KAPOW”;
私有注册验证程序classToTest;
@嘲弄
私人注册;
@嘲弄
私有RegionExceptionType mockRegionExceptionType;//使用RegionException的实际类型。
@以前
公共无效预测试设置()
{
initMocks(this);
ReflectionTestUtils.setField(classToTest,“supportedSpecializations”,VALUE\u SUPPORTED\u SPECIALIZATIONS);
doReturn(值\异常\消息).when(mockRegionExceptionType).getMessage();
doReturn(mockRegionExceptionType).when(mockRegion.regionException();
}
@试验
公共无效初始化\u allGood\u success()
{
classToTest.initialize(模拟注册);
…断言一些东西。
…也许可以验证一些东西。
}
}

我认为对您来说,最好的选择是在您的
RegistrationValidator.class
中使用构造函数注入,以便您可以在需要时直接为测试分配模拟值或测试值。例如:

@Component
class ExampleClass {

    final String text

    // Use @Autowired to get @Value to work.
    @Autowired
    ExampleClass(
        // Refer to configuration property
        // app.message.text to set value for 
        // constructor argument message.
        @Value('${app.message.text}') final String text) {
        this.text = text
    }

}
通过这种方式,您可以将模拟值设置为用于单元测试的变量。 是的,您是对的自定义构造函数不是这里的选项,那么您可以引入一个配置类,在该类中,您可以从yml或验证程序中的property和autowire读取这些值。这应该对您有用

您可以在单独的
test.yml
test.properties
中提供
@Value
属性,并指定在运行集成测试时使用这些属性。在这种情况下,您应该能够解析这些值

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ExampleApplication.class)
@TestPropertySource(locations="classpath:test.properties")
public class ExampleApplicationTests {

}

@TestPropertySource
注释具有更高的优先顺序,它应该解析您的值。

我认为对您来说最好的选择是在您的
RegistrationValidator.class
中使用构造函数注入,以便您可以在需要时直接为测试分配模拟值或测试值。例如:

@Component
class ExampleClass {

    final String text

    // Use @Autowired to get @Value to work.
    @Autowired
    ExampleClass(
        // Refer to configuration property
        // app.message.text to set value for 
        // constructor argument message.
        @Value('${app.message.text}') final String text) {
        this.text = text
    }

}
通过这种方式,您可以将模拟值设置为用于单元测试的变量。 是的,您是对的自定义构造函数不是这里的选项,那么您可以引入一个配置类,在该类中,您可以从yml或验证程序中的property和autowire读取这些值。这应该对您有用

您可以在单独的
test.yml
test.properties
中提供
@Value
属性,并指定在运行集成测试时使用这些属性。在这种情况下,您应该能够解析这些值

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ExampleApplication.class)
@TestPropertySource(locations="classpath:test.properties")
public class ExampleApplicationTests {

}
@TestPropertySource
注释具有更高的pr
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@AutoConfigureMockMvc
public class StudentValidatorTest {

    @Autowired
    private StudentController mockRestController;

    @MockBean
    private StudentService mockStudentService;

    @MockBean
    private ValidationExceptionTranslator mockExceptionTranslator;

    @Autowired
    private MockMvc mockMvc;

    private static final String VALIDATION_SUCCESSFUL = "success";
    private static final String VALIDATION_FAILED = "failed";

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        this.mockMvc = MockMvcBuilders.standaloneSetup(mockRestController).build();

        doReturn(
            ResponseEntity.status(HttpStatus.OK)
            .header("Content-Type", "text/html; charset=utf-8")
            .body(VALIDATION_SUCCESSFUL))
        .when(mockStudentService).insertStudent(Mockito.any());

        doReturn(
                ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .header("Content-Type", "application/json")
                .body(VALIDATION_FAILED))
        .when(mockExceptionTranslator).translate(Mockito.any());
    }

//...and tests...