java - unitaria - ¿Cómo obtengo Spring MVC para invocar la validación en una prueba JUnit?
software para pruebas unitarias en java (3)
Tengo un POJO llamado Navegador que he anotado con anotaciones de Hibernate Validator.
import org.hibernate.validator.constraints.NotEmpty;
public class Browser {
@NotEmpty
private String userAgent;
@NotEmpty
private String browserName;
...
}
He escrito la siguiente prueba unitaria que intenta verificar que mi método de controlador detecta errores de validación.
@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());
}
Aquí está el método add () de mi Controlador:
@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;
}
El problema que estoy experimentando es que el resultado nunca tiene errores, por lo que es como si @Valid no fuera reconocido. Traté de agregar lo siguiente a mi clase de prueba, pero no resuelve el problema.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"file:path-to/WEB-INF/spring-mvc-servlet.xml"})
¿Alguien sabe cómo conseguiré que @Valid sea reconocido (y validado) cuando se prueba con JUnit?
Gracias,
Mate
Básicamente, usted instancia un POJO con this.controller = new MyController()
, luego llama a su método this.controller.add(...)
. Simplemente Java simple con un objeto simple, sin ningún contexto: @Valid no se tiene en cuenta.
@ContextConfiguration solo cargará sus posibles beans, con posibles validadores personalizados y demás, pero no hará la magia de procesar @Valid.
Lo que necesita es algo para emular una solicitud al método de add
controlador. Emule completamente, validación incluida. No estuvo lejos de hacerlo, ya que utilizó algunas instalaciones de prueba de primavera (para instanciar una MockHttpServletRequest).
Si usa Spring 3.0.xo menos , debe hacer
new AnnotationMethodHandlerAdapter()
.handle(request, new MockHttpServletResponse(), this.controller);
para hacer que funcione
Si usa Spring 3.1+ , la solución anterior no funcionará ( consulte este enlace para obtener más información ). Tendrá que usar esta biblioteca (del equipo de Spring, para que su sonido no se preocupe), mientras espera que se integre en la próxima versión de Spring. Entonces tendrás que hacer algo como
myMockController = MockMvcBuilders.standaloneSetup(new MyController()).build();
myMockController.perform(get("/browser/create")).andExpect(...);
¡También eche un vistazo a estas diapositivas muy interesantes de Rossen Stoyanchev (la parte de la que estamos hablando aquí comienza en la diapositiva # 116)!
Nota: No entraré en el debate de si este tipo de prueba se considera prueba unitaria o prueba de integración. Algunos dirían que esto es más bien una prueba de integración que estamos haciendo aquí, ya que emulamos la ruta completa de una solicitud. Pero por otro lado, todavía puedes burlar tu controlador con anotaciones @Mock de Mockito (o hacer cosas similares con cualquier otro marco burlón), por lo que otros dirán que puedes reducir el alcance de la prueba a "pruebas unitarias" casi puras. . Por supuesto, puede probar el controlador de manera alternativa y puramente con Java + un marco burlón, pero en este caso no le permitirá probar la validación @Valid. ¡Haz tu elección! :)
La validación se realiza antes de la llamada al controlador, por lo que su prueba no invoca esta validación.
Existe otro enfoque para probar los controladores, donde no se invoca directamente al controlador. En su lugar, construye y llama a la URL en la que está asignado el controlador. Aquí hay un buen ejemplo de cómo hacer esto: 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 (necesita usar el informe de primavera de hitos):
<!-- 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>
NOTA: la lib de spring-mvc-test todavía no está lista para producción. Hay algunas lagunas en la implementación. Creo que está previsto que se implemente por completo para la primavera 3.2.
Este enfoque es una gran idea ya que prueba completamente sus controladores. Es fácil estropear las asignaciones de su controlador, por lo que realmente deben someterse a pruebas unitarias.
Los validadores se llaman antes que los métodos del controlador que se invocan, durante el proceso de vincular la solicitud a los parámetros del método. Dado que en este caso está invocando directamente el método del controlador, los pasos de enlace y validación se están pasando por alto.
La forma de hacerlo funcionar es hacer la llamada al controlador a través de la pila Spring MVC. Hay algunas maneras de hacerlo, creo que la mejor manera es usar spring-test-mvc, que proporciona un buen mecanismo para llamar a través de la pila.
Otra forma de llamar a través de la pila es inyectar en HandlerAdapter a la prueba de esta manera:
@Autowired
private RequestMappingHandlerAdapter handlerAdapter;
Luego en la prueba:
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);