Как мне получить Spring MVC для активации проверки в тесте JUnit?

У меня есть POJO, называемый браузером, который я аннотировал с аннотациями Hibernate Validator.

import org.hibernate.validator.constraints.NotEmpty;

public class Browser {

    @NotEmpty
    private String userAgent;
    @NotEmpty
    private String browserName;

...

}

Я написал следующий unit test, который пытается проверить, что мой метод контроллера обнаруживает ошибки проверки.

@Test
public void testInvalidData() throws Exception {
    Browser browser = new Browser("opera", null);
    MockHttpServletRequest request = new MockHttpServletRequest();

    BindingResult errors = new DataBinder(browser).getBindingResult();
    // controller is initialized in @Before method
    controller.add(browser, errors, request);
    assertEquals(1, errors.getErrorCount());
}

Здесь мой метод addler():

@RequestMapping(value = "/browser/create", method = RequestMethod.POST)
public String add(@Valid Browser browser, BindingResult result, HttpServletRequest request) throws Exception {
    if (result.hasErrors()) {
        request.setAttribute("errorMessage", result.getAllErrors());
        return VIEW_NAME;
    }

    browserManager.save(browser);

    request.getSession(false).setAttribute("successMessage",
            String.format("Browser %s added successfully.", browser.getUserAgent()));

    return "redirect:/" + VIEW_NAME;
}

Проблема, с которой я столкнулся, заключается в том, что результат никогда не имеет ошибок, поэтому он не может распознаваться как @Valid. Я попытался добавить следующее к моему тестовому классу, но это не решает проблему.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"file:path-to/WEB-INF/spring-mvc-servlet.xml"})

Кто-нибудь знает, как я получаю @Valid для распознавания (и проверки) при тестировании с помощью JUnit?

Спасибо,

Matt

Ответ 1

Проверка выполняется до вызова контроллера, поэтому ваш тест не вызывает эту проверку.

Существует другой подход к тестированию контроллеров, где вы не вызываете контроллер напрямую. Вместо этого вы создаете и вызываете URL-адрес, на который настроен контроллер. Вот хороший пример того, как это сделать: http://rstoyanchev.github.com/spring-31-and-mvc-test/#1

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=WebContextLoader.class, locations = {"classpath:/META-INF/spring/applicationContext.xml", "classpath:/META-INF/spring/applicationContext-test-override.xml", "file:src/main/webapp/WEB-INF/spring/webmvc-config.xml"})
public class MyControllerTest {
@Autowired
WebApplicationContext wac;
MockMvc mockMvc;

@Before
public void setup() {
    this.mockMvc = MockMvcBuilders.webApplicationContextSetup(this.wac).build();
}

@Test
@Transactional
public void testMyController() throws Exception {
    this.mockMvc.perform(get("/mycontroller/add?param=1").accept(MediaType.TEXT_HTML))
    .andExpect(status().isOk())
    .andExpect(model().attribute("date_format", "M/d/yy h:mm a"))
    .andExpect(model().attribute("myvalue", notNullValue()))
    .andExpect(model().attribute("myvalue", hasSize(2)))
    .andDo(print());
}
}

POM (необходимо использовать spring вехой репо):

    <!-- required for spring-test-mvc -->
    <repository>
        <id>spring-maven-milestone</id>
        <name>Spring Maven Milestone Repository</name>
        <url>http://maven.springframework.org/milestone</url>
    </repository>
...
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test-mvc</artifactId>
        <version>1.0.0.M1</version>
        <scope>test</scope>
    </dependency>

ПРИМЕЧАНИЕ. spring -mvc-test lib еще не готов к выпуску. В реализации есть некоторые пробелы. Я думаю, что его планируется полностью реализовать для spring 3.2.

Этот подход - отличная идея, поскольку он полностью тестирует ваши контроллеры. Его легко повредить сопоставления контроллеров, поэтому они действительно нуждаются в тестировании устройств.

Ответ 2

Валидаторы вызываются перед вызовами методов контроллера - в процессе привязки запроса к параметрам метода. Поскольку в этом случае вы вызываете метод контроллера напрямую, шаги привязки и проверки проверяются.

Способ заставить его работать, это сделать вызов контроллера через стек Spring MVC. Существует несколько способов сделать это, я считаю, что лучший способ - использовать spring-test-mvc, который обеспечивает хороший механизм для вызова через стек.

Другой способ вызова через стек - это ввести в HandlerAdapter тест следующим образом:

@Autowired
private RequestMappingHandlerAdapter handlerAdapter;

Тогда в тесте:

MockHttpServletRequest request = new MockHttpServletRequest("POST","/browser/create");
MockHttpServletResponse response = new MockHttpServletResponse();
httpRequest.addParameter(.... );//whatever is required to create Browser..
ModelAndView modelAndView = handlerAdapter.handle(httpRequest, response, handler);

Ответ 3

В основном вы создавали POJO с помощью this.controller = new MyController(), а затем вызывали его метод this.controller.add(...). Просто простая Java с простым объектом, без какого-либо контекста: @Valid не принимается во внимание.

@ContextConfiguration просто загрузит ваш возможный beans, с возможными настраиваемыми валидаторами и т.д., но он не будет использовать магию обработки @Valid.

Вам нужно что-то эмулировать запрос к методу контроллера add. Полностью подражать ему, включая валидацию. Вы были недалеко от этого, так как вы использовали некоторые средства тестирования Spring (для создания экземпляра MockHttpServletRequest).

Если вы используете Spring 3.0.x или меньше, вам нужно сделать

new AnnotationMethodHandlerAdapter()
      .handle(request, new MockHttpServletResponse(), this.controller);

чтобы он работал.

Если вы используете Spring 3.1 +, это решение не будет работать (см. эту ссылку для получения дополнительной информации)! Вам понадобится эта библиотека (из команды Spring, так что это звучит не волнуйтесь), ожидая их интеграции в следующей версии Spring. Тогда вам нужно будет сделать что-то вроде

myMockController = MockMvcBuilders.standaloneSetup(new MyController()).build();
myMockController.perform(get("/browser/create")).andExpect(...);

Также посмотрите на эти интересные слайды от Rossen Stoyanchev (часть, о которой мы говорим здесь, начинается со слайда # 116)!

Примечание. Я не буду вступать в дискуссию о том, считается ли этот тип тестирования модульным тестированием или интеграционным тестированием. Некоторые сказали бы, что это скорее интеграционное тестирование, которое мы делаем здесь, поскольку мы эмулируем полный путь запроса. Но с другой стороны вы все еще можете высмеивать своего контроллера с помощью аннотаций @Mock от Mockito (или делать подобные вещи с любой другой насмешкой), поэтому некоторые другие могут сказать, что вы можете уменьшить объем теста до почти чистого "модульного тестирования", Конечно, вы можете альтернативно и чисто unit test ваш контроллер с простой старой Java + издевательской структурой, но в этом случае это не позволит вам проверить валидацию @Valid. Сделай свой выбор!:)