Spring-Boot 单元测试:ConstraintValidator 中的@Value
Posted
技术标签:
【中文标题】Spring-Boot 单元测试:ConstraintValidator 中的@Value【英文标题】:Spring-Boot UnitTest: @Value in ConstraintValidator 【发布时间】:2019-10-19 04:34:58 【问题描述】:我目前正在提供覆盖 - 通过 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 我目前正在使用以下 sn-p 进行测试:
@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) 注释我的测试类,但验证测试仍然失败,原因是@Value 未解决。我可能错了,但我认为 ConstraintValidator 的实例是在我们到达 restController 之前创建的,所以 MockMVC perform(...) 调用不能简单地确保验证器中的适当 @Value 得到注入supportedSpecializations。
【问题讨论】:
你能发布你的单元测试吗?只是为了确定,但似乎您没有加载弹簧上下文,因此@Value 中没有注入任何值。 @XavierBouclet 我更新了帖子,请看一下 你在嘲笑你的控制器并嘲笑很多东西。默认情况下,Spring 不为处理约束验证器做任何事情。您将需要一个适当的@WebMvcTest
或完整的@SpringBootTest
测试,否则@Value
将无法解决。紧接着 Spring 并没有真正控制创建这些验证器的实例,而是验证器实现(在本例中为休眠),并且根据版本,Spirng 甚至不会处理该类。
【参考方案1】:
通过以下方式解决了该问题: 在测试类中添加了以下注释
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@AutoConfigureMockMvc
然后自动装配 controller 和 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...
【讨论】:
【参考方案2】:是的,
使用ReflectionTestUtil
。
使用ReflectionTestUtil.setField
设置supportedSpecializations
的值
setup()
方法(junit @Before 注释方法(junit > 1.4)中。
更多详情
我建议不要使用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.
【讨论】:
您能简单描述一下如何做到这一点吗?根据 Baeldung 的自定义约束教程,我通过 MockMVC 执行方法调用该功能。有没有办法通过 MockMVC / Mocked RestController 提取和模拟某些 constraintValidator?【参考方案3】:我认为对您来说最好的选择是在您的 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 或属性中读取这些值并在验证器中自动装配。这应该适合你。
或者
您可以在单独的test.yml
或test.properties
中提供@Value
属性,并指定在运行集成测试时使用该属性。在这种情况下,您应该能够解析这些值。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ExampleApplication.class)
@TestPropertySource(locations="classpath:test.properties")
public class ExampleApplicationTests
@TestPropertySource
注释具有更高的优先顺序,它应该解析您的值。
【讨论】:
不幸的是,两者都没有帮助。我用细节更新了这个问题。问题是,我无法为 ConstraintValidator 编写自定义构造函数,并且第二种解决方案不适用于较新的 Spring-Boot 应用程序。我使用 2.1.0 是的,你是对的,然后你可以引入一个配置类,你可以在其中从 yml 或属性中读取这些值并在验证器中自动装配。这应该适合你。以上是关于Spring-Boot 单元测试:ConstraintValidator 中的@Value的主要内容,如果未能解决你的问题,请参考以下文章