1

我目前正在提供覆盖 - 通过 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;
    }
}

由于 @Value 注释的定义属性未注入该字段,单元测试现在失败。我不确定ReflectionTestUtils是否对我有帮助,因此非常感谢有关如何在 UnitTests 中注入所需值的任何建议。


Spring 版本是 2.1.0 我目前正在使用以下代码段进行测试:

@InjectMocks
private StudentController mockRestController;

@Mock
private StudentService mockStudentService;

@Mock
private ValidationExceptionTranslator mockExceptionTranslator;

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

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

@Test
public void testValidation_UnsupportedSpecialization() throws Exception {

    MvcResult mvcResult = mockMvc.perform(
            post("/Students").contentType(MediaType.APPLICATION_JSON_UTF8).content(
                    "{\"registrationData\":{\"spec\":\"unsupported\"}}"))
            .andExpect(status().isBadRequest())
            .andReturn();

    assertEquals(VALIDATION_FAILED, mvcResult.getResponse().getContentAsString());

    verify(mockExceptionTranslator, times(1)).translate(Mockito.any());
    verify(mockStudentService, times(0)).insertStudent(Mockito.any());
}

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

4

3 回答 3

0

是的,使用ReflectionTestUtil.

用于在单元测试的 方法 (junit < 1.4) 或带注释的方法 (junit > 1.4) 中ReflectionTestUtil.setField设置 的值。supportedSpecializationssetup()@Before


我建议不要将更多详细信息MockMVC用于您的单元测试;它适用于集成测试,但不适用于单元测试。

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

这是一个简单的例子:

public class TestRegistrationValidator
{
  private static final String VALUE_EXCEPTION_MESSAGE = "VALUE_EXCEPTION_MESSAGE";
    private static final String VALUE_SUPPORTED_SPECIALIZATIONS = "BLAMMY,KAPOW";

    private RegistrationValidator classToTest;

    @Mock
    private Registration mockRegistration;

    @Mock
    private RegionExceptionType mockRegionExceptionType; // use the actual type of regionExcpeption.

    @Before
    public void preTestSetup()
    {
        MockitoAnnotations.initMocks(this);

        ReflectionTestUtils.setField(classToTest, "supportedSpecializations", VALUE_SUPPORTED_SPECIALIZATIONS);

        doReturn(VALUE_EXCEPTION_MESSAGE).when(mockRegionExceptionType).getMessage();

        doReturn(mockRegionExceptionType).when(mockRegion).regionException();
    }

    @Test
    public void initialize_allGood_success()
    {
        classToTest.initialize(mockRegistration);

        ...assert some stuff.
        ...perhaps verify some stuff.
    }
}
于 2019-06-03T16:00:00.030 回答
0

通过以下方式解决了问题:在测试类中添加了以下注释

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@AutoConfigureMockMvc

然后自动装配控制器mockMVC,最后用 Spring 的@MockBean注释服务和翻译器

所以目前它看起来像这样:

@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...
于 2019-06-04T14:12:16.217 回答
0

我认为对你来说最好的选择是在你的 中使用构造函数注入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 或属性中读取这些值,并在验证器中自动装配它们。这应该适合你。

或者

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

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

}

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

于 2019-06-04T06:33:06.277 回答