Spring-Boot UnitTest: @Value в ConstraintValidator - PullRequest
1 голос
/ 03 июня 2019

В настоящее время я предоставляю покрытие - проверяю проверку моего DTO с помощью запроса вызова MockMVC.Я недавно ввел новое поле в ConstraintValidator для регистрации, SupportSpecializations, из которого я вставляю значения из 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());
}

Я пытался пометить свой класс Test с помощью @ RunWith (SpringRunner.class) и @ SpringBootTest (классы= Application.class) , но проверка по-прежнему не проходит из-за того, что @Value не разрешается.Я могу ошибаться, но я думаю, что экземпляр ConstraintValidator создается до того, как мы достигнем restController, поэтому вызов MockMVC execute (...) не может просто убедиться, что соответствующее значение @Value в валидаторе вставленов поддерживаемые специализации.

Ответы [ 3 ]

0 голосов
/ 04 июня 2019

Лучший вариант, который я считаю для вас, - это использовать инжектор конструктора в вашем 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 имеет более высокий порядок приоритета и должна разрешать ваши значения.

0 голосов
/ 04 июня 2019

Решил проблему следующим образом: Добавил следующие аннотации в класс тестирования

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

Затем автоматически подключил контроллер и mockMVC , наконец, аннотировал сервис ипереводчик с Spring's @ 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...
0 голосов
/ 03 июня 2019

Да, используйте ReflectionTestUtil.

Используйте ReflectionTestUtil.setField, чтобы установить значение supportedSpecializations в setup() метод (junit <1.4) или в аннотированном методе <code>@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.
    }
}
...