web-dev-qa-db-fra.com

Comment tester à l'unité un contrôleur Spring MVC en utilisant @PathVariable?

J'ai un simple contrôleur annoté similaire à celui-ci:

@Controller
public class MyController {
  @RequestMapping("/{id}.html")
  public String doSomething(@PathVariable String id, Model model) {
    // do something
    return "view";
  }
}

et je veux le tester avec un test unitaire comme celui-ci:

public class MyControllerTest {
  @Test
  public void test() {
    MockHttpServletRequest request = new MockHttpServletRequest();
    request.setRequestURI("/test.html");
    new AnnotationMethodHandlerAdapter()
      .handle(request, new MockHttpServletResponse(), new MyController());
    // assert something
  }
}

Le problème est que la méthode AnnotationMethodHandlerAdapter.handler () lève une exception:

Java.lang.IllegalStateException: Could not find @PathVariable [id] in @RequestMapping
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter$ServletHandlerMethodInvoker.resolvePathVariable(AnnotationMethodHandlerAdapter.Java:642)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolvePathVariable(HandlerMethodInvoker.Java:514)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolveHandlerArguments(HandlerMethodInvoker.Java:262)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.Java:146)
52
martiner

Depuis le printemps 3.2, il existe un moyen approprié de tester cela, d'une manière élégante et facile. Vous pourrez faire des choses comme ça:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("servlet-context.xml")
public class SampleTests {

  @Autowired
  private WebApplicationContext wac;

  private MockMvc mockMvc;

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

  @Test
  public void getFoo() throws Exception {
    this.mockMvc.perform(get("/foo").accept("application/json"))
        .andExpect(status().isOk())
        .andExpect(content().mimeType("application/json"))
        .andExpect(jsonPath("$.name").value("Lee"));
  }
}

Pour plus d'informations, consultez http://blog.springsource.org/2012/11/12/spring-framework-3-2-rc1-spring-mvc-test-framework/

37
Clint Eastwood

J'appellerais ce que vous recherchez après un test d'intégration basé sur la terminologie du manuel de référence Spring. Que diriez-vous de faire quelque chose comme:

import static org.springframework.test.web.ModelAndViewAssert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({/* include live config here
    e.g. "file:web/WEB-INF/application-context.xml",
    "file:web/WEB-INF/dispatcher-servlet.xml" */})
public class MyControllerIntegrationTest {

    @Inject
    private ApplicationContext applicationContext;

    private MockHttpServletRequest request;
    private MockHttpServletResponse response;
    private HandlerAdapter handlerAdapter;
    private MyController controller;

    @Before
    public void setUp() {
       request = new MockHttpServletRequest();
       response = new MockHttpServletResponse();
       handlerAdapter = applicationContext.getBean(HandlerAdapter.class);
       // I could get the controller from the context here
       controller = new MyController();
    }

    @Test
    public void testDoSomething() throws Exception {
       request.setRequestURI("/test.html");
       final ModelAndView mav = handlerAdapter.handle(request, response, 
           controller);
       assertViewName(mav, "view");
       // assert something
    }
}

Pour plus d'informations, j'ai écrit un entrée de blog sur les tests d'intégration des annotations Spring MVC .

46
scarba05

Un cadre prometteur pour tester Spring MVC https://github.com/SpringSource/spring-test-mvc

10
tbruyelle

Si vous utilisez Spring 3.0.x.

Ici, je suggère une fusion des réponses d'Emil et de scarba05 en utilisant spring-test et non spring-test-mvc. Veuillez ignorer cette réponse et vous référer aux exemples spring-test-mvc si vous utilisez Spring 3.2.x ou une version ultérieure

MyControllerWithParameter.Java

@Controller
public class MyControllerWithParameter {
@RequestMapping("/testUrl/{pathVar}/some.html")
public String passOnePathVar(@PathVariable String pathVar, ModelMap model){
    model.addAttribute("SomeModelAttribute",pathVar);
    return "viewName";
}
}

MyControllerTest.Java

import static org.springframework.test.web.ModelAndViewAssert.assertViewName;
import Java.util.HashMap;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.ModelAndViewAssert;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = 
    {"file:src\\main\\webapp\\WEB-INF\\spring\\services\\servlet-context.xml" 
    })
public class MyControllerTest {

private MockHttpServletRequest request;
private MockHttpServletResponse response;
private HandlerAdapter handlerAdapter;

@Before
public void setUp() throws Exception {
    request = new MockHttpServletRequest();
    response = new MockHttpServletResponse();
    this.handlerAdapter = applicationContext.getBean(AnnotationMethodHandlerAdapter.class);
}

//  Container beans
private MyControllerWithParameter myController;
private ApplicationContext applicationContext;
public ApplicationContext getApplicationContext() {
    return applicationContext;
}
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
}
public MyControllerWithParameter getMyController() {
    return myController;
}
@Autowired
public void setMyController(MyControllerWithParameter myController) {
    this.myController = myController;
}

@Test
public void test() throws Exception {
    request.setRequestURI("/testUrl/Irrelavant_Value/some.html");
    HashMap<String, String> pathvars = new HashMap<String, String>();
    // Populate the pathVariable-value pair in a local map
    pathvars.put("pathVar", "Path_Var_Value");
    // Assign the local map to the request attribute concerned with the handler mapping 
    request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, pathvars);

    final ModelAndView modelAndView = this.handlerAdapter.handle(request, response, myController);

    ModelAndViewAssert.assertAndReturnModelAttributeOfType(modelAndView, "SomeModelAttribute", String.class);
    ModelAndViewAssert.assertModelAttributeValue(modelAndView, "SomeModelAttribute", "Path_Var_Value");
    ModelAndViewAssert.assertViewName(modelAndView, "viewName");
}

}

3
Sym-Sym

Le message d'exception fait référence à une variable "feed", qui n'est pas présente dans votre exemple de code, elle est probablement causée par quelque chose que vous ne nous avez pas montré.

De plus, votre test teste Spring et votre propre code. Est-ce vraiment ce que tu veux faire?

Il est préférable de supposer que Spring fonctionne (ce qu'il fait) et de tester simplement votre propre classe, c'est-à-dire d'appeler MyController.doSomething() directement. C'est l'un des avantages de l'approche d'annotation - vous n'avez pas besoin d'utiliser de fausses demandes et réponses, vous utilisez simplement des POJO de domaine.

3
skaffman

J'ai constaté que vous pouvez insérer manuellement un mappage PathVariable dans l'objet de demande. Ceci est clairement non idéal mais semble fonctionner. Dans votre exemple, quelque chose comme:

@Test
public void test() {
    MockHttpServletRequest request = new MockHttpServletRequest();
    request.setRequestURI("/test.html");
    HashMap<String, String> pathvars = new HashMap<String, String>();
    pathvars.put("id", "test");
    request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, pathvars);
    new AnnotationMethodHandlerAdapter().handle(request, new MockHttpServletResponse(), new MyController());
   // assert something
}

Je serais certainement intéressé à trouver une meilleure option.

2
Emil Sit

Je ne suis pas sûr que ma réponse originale va aider avec @PathVariable. Je viens d'essayer de tester un @PathVariable et j'obtiens l'exception suivante:

org.springframework.web.bind.annotation.support.HandlerMethodInvocationException: échec de l'appel de la méthode du gestionnaire [public org.springframework.web.servlet.ModelAndView test.MyClass.myMethod (test.SomeType)]; l'exception imbriquée est Java.lang.IllegalStateException: impossible de trouver @PathVariable [parameterName] dans @RequestMapping

La raison en est que les variables de chemin dans la requête sont analysées par un intercepteur. L'approche suivante fonctionne pour moi:

import static org.springframework.test.web.ModelAndViewAssert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"file:web/WEB-INF/application-context.xml",
        "file:web/WEB-INF/dispatcher-servlet.xml"})    
public class MyControllerIntegrationTest {

    @Inject
    private ApplicationContext applicationContext;

    private MockHttpServletRequest request;
    private MockHttpServletResponse response;
    private HandlerAdapter handlerAdapter;

    @Before
    public void setUp() throws Exception {
        this.request = new MockHttpServletRequest();
        this.response = new MockHttpServletResponse();

        this.handlerAdapter = applicationContext.getBean(HandlerAdapter.class);
    }

    ModelAndView handle(HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        final HandlerMapping handlerMapping = applicationContext.getBean(HandlerMapping.class);
        final HandlerExecutionChain handler = handlerMapping.getHandler(request);
        assertNotNull("No handler found for request, check you request mapping", handler);

        final Object controller = handler.getHandler();
        // if you want to override any injected attributes do it here

        final HandlerInterceptor[] interceptors =
            handlerMapping.getHandler(request).getInterceptors();
        for (HandlerInterceptor interceptor : interceptors) {
            final boolean carryOn = interceptor.preHandle(request, response, controller);
            if (!carryOn) {
                return null;
            }
        }

        final ModelAndView mav = handlerAdapter.handle(request, response, controller);
        return mav;
    }

    @Test
    public void testDoSomething() throws Exception {
        request.setRequestURI("/test.html");
        request.setMethod("GET");
        final ModelAndView mav = handle(request, response);
        assertViewName(mav, "view");
        // assert something else
    }

J'ai ajouté un nouveau billet de blog sur test d'intégration des annotations mvc de printemps

1
scarba05