8 votes

Spring-Test-MVC / MockServletContext - contenu vide en test mais fonctionnel sur Tomcat

Nous essayons de mettre en place Spring-Test-MVC pour notre application web Spring-MVC. Nous avons commencé par utiliser freemarker et tout allait bien. Nous avons décidé de ne pas le faire et nous essayons maintenant de le mettre en place avec JSP. Lorsque l'application de test est déployée sur un Tomcat, elle fonctionne. Lorsque nous exécutons le test simple :

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, locations = { "file:src/main/webapp/WEB-INF/servlet-context.xml" })
public class SkelletonTest {

    @Inject
    private MockMvc mockMvc;

    @Test
    public void homeTest() throws Exception {
        mockMvc.perform(get("/")).andExpect(status().isOk())
                .andExpect(content().type("text/html;charset=ISO-8859-1"))
                .andExpect(content().string(containsString("Hello World!")));
    }

il est dit : content type not set ou si elle est supprimée, le contenu sera simplement vide. Le contrôleur sera cependant appelé, donc le mapping doit fonctionner.

Cela suggère donc fortement que la vue n'est pas rendue pour nos tests, mais je n'ai aucune idée de la configuration qui pourrait me manquer.

Voici notre fichier servlet-context.xml :

<context:component-scan base-package="package.to.controllers" />
<mvc:annotation-driven />

<bean id="viewResolver"
    class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass"
        value="org.springframework.web.servlet.view.JstlView" />
    <property name="exposeContextBeansAsAttributes" value="true" />
    <property name="prefix" value="/views/" />
    <property name="suffix" value=".jsp" />
</bean>

Le WebContextLoader :

public class WebContextLoader extends GenericWebContextLoader {
    public WebContextLoader() {
        super("src/main/webapp", false);
    }
}

GenericWebContextLoader est l'original de spring-test-mvc.

Le MockMvc est configuré comme un Bean de la manière suivante :

@Configuration
public class TestConfig {

    @Inject
    private WebApplicationContext wac;

    @Bean   
    public MockMvc create(){
        return (MockMvcBuilders.webApplicationContextSetup(this.wac).build());
    }
}

Web.xml n'est pas utilisé par le framework de test et ne devrait pas avoir d'importance puisqu'il fonctionnait auparavant.

Je pense qu'il doit y avoir une configuration supplémentaire dans le contexte de la servlet. Il est chargé, ce que j'ai vérifié, mais alors que pour l'application déployée par Tomcat, c'est important, ce que j'ai défini pour le préfixe et le suffixe sera simplement ignoré par le test.

Je ne suis pas sûr que la trace d'erreur soit d'une grande utilité, mais la voici :

java.lang.AssertionError: Content type not set
    at org.springframework.test.web.AssertionErrors.fail(AssertionErrors.java:35)
    at org.springframework.test.web.AssertionErrors.assertTrue(AssertionErrors.java:57)
    at org.springframework.test.web.server.result.ContentResultMatchers$1.match(ContentResultMatchers.java:59)
    at org.springframework.test.web.server.MockMvc$1.andExpect(MockMvc.java:84)
    at our.package.SkelletonTest.homeTest(SkelletonTest.java:30)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

Et le résultat du test :

2012-06-15 10:41:04 TestContextManager [INFO] @TestExecutionListeners is not present for class [class package.to.test.SkelletonTest]: using defaults.
2012-06-15 10:41:05 XmlBeanDefinitionReader [INFO] Loading XML bean definitions from URL [file:src/main/webapp/WEB-INF/servlet-context.xml]
2012-06-15 10:41:05 ClassPathBeanDefinitionScanner [INFO] JSR-330 'javax.inject.Named' annotation found and supported for component scanning
2012-06-15 10:41:05 GenericWebApplicationContext [INFO] Refreshing org.springframework.web.context.support.GenericWebApplicationContext@158539f: startup date [Fri Jun 15 10:41:05 CEST 2012]; root of context hierarchy
2012-06-15 10:41:05 AutowiredAnnotationBeanPostProcessor [INFO] JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2012-06-15 10:41:05 DefaultListableBeanFactory [INFO] Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@c64bc2: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,testConfig,freemarkerController,homeController,tableService,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#0,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,viewResolver,org.springframework.context.annotation.ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor#0,create]; root of factory hierarchy
2012-06-15 10:41:05 RequestMappingHandlerMapping [INFO] Mapped "{[/],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.web.servlet.ModelAndView package.to.controller.HomeController.index()
2012-06-15 10:41:05 RequestMappingHandlerMapping [INFO] Mapped "{[/test],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String package.to.controller.HomeController.test(org.springframework.ui.Model)
2012-06-15 10:41:06 GenericWebContextLoader$1 [INFO] Initializing Spring FrameworkServlet ''
2012-06-15 10:41:06 TestDispatcherServlet [INFO] FrameworkServlet '': initialization started
2012-06-15 10:41:06 TestDispatcherServlet [INFO] FrameworkServlet '': initialization completed in 32 ms
2012-06-15 10:41:06 GenericWebApplicationContext [INFO] Closing org.springframework.web.context.support.GenericWebApplicationContext@158539f: startup date [Fri Jun 15 10:41:05 CEST 2012]; root of context hierarchy
2012-06-15 10:41:06 DefaultListableBeanFactory [INFO] Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@c64bc2: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,testConfig,freemarkerController,homeController,tableService,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#0,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,viewResolver,org.springframework.context.annotation.ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor#0,create]; root of factory hierarchy

Merci donc pour toutes les suggestions qui m'aideront à localiser le problème !

Btw : Je ne voulais pas que cela devienne plus long, donc j'ai sauté le pom. Nous utilisons Spring 3.1, spring-test-mvc 1.0.0..BUILD-SNAPSHOT, jsp-ap 2.2, jstl 1.2, ... Si vous avez besoin d'en savoir plus, j'essaierai de l'uploader quelque part...


Editar

N'hésitez pas à me faire savoir si vous avez besoin de plus d'informations ou si vous ne pouvez pas répondre à ma question. J'ai vraiment besoin de comprendre et je ne sais pas du tout par où commencer. Toute réflexion ou commentaire est le bienvenu.


Edit2

La méthode d'impression a donné les résultats suivants :

MockHttpServletRequest:
         HTTP Method = GET
         Request URI = /
          Parameters = {}
             Headers = {}

             Handler:
                Type = package.to.controller.HomeController
              Method = public org.springframework.web.servlet.ModelAndView package.to.controller.HomeController.index()

  Resolved Exception:
                Type = null

        ModelAndView:
           View name = index
                View = null
           Attribute = welcome
               value = Hello World!

            FlashMap:

MockHttpServletResponse:
              Status = 200
       Error message = null
             Headers = {}
        Content type = null
                Body = 
       Forwarded URL = /views/index.jsp
      Redirected URL = null
             Cookies = []

Ce qui montre mieux le problème, mais pas la solution...


éditer3

Je viens d'apprendre ce qui suit :

JSP nécessite un conteneur de servlet. Il semble donc que je ne puisse pas tester mes pages de cette manière... Si quelqu'un a une idée pour contourner ce problème, qu'il me le fasse savoir...

9voto

John Lockwood Points 1545

@Biju -- J'apprécie cette réponse qui m'a évité un voyage, et je n'essaie pas de tirer sur le messager ici, mais je dois dire pour le bénéfice de quiconque dans l'équipe Spring qui pourrait être motivé pour construire quelque chose de mieux, je trouve que MockMVC est plutôt un exercice de trivialité et de futilité. Tout d'abord, l'authentification Spring n'est pas directement supportée. D'accord, il suffit de parcourir StackOverflow pour trouver une solution de contournement. Ensuite, j'ai découvert que si vous avez dans votre configuration de contexte Spring, tous les chemins que vous pouvez inventer reviennent avec "OK" même s'ils devraient renvoyer "notFound". OK, peu importe, enlevez ça et n'en parlons plus jamais :) Et maintenant, nous découvrons que MockMVC n'est en fait que MockMC -- aucun traitement de vue n'a lieu. Donc en fin de compte, ce logiciel est bon pour tester des applications qui ne contiennent ni sécurité ni vues -- ce qui serait, quoi, exactement, des applications toy JSON ?

Et ce n'est pas vraiment une réponse, c'est un coup de gueule, ce qui veut dire que maintenant MockMVC va me coûter la réputation de StackOverflow aussi ! :) Hulk smash !

[EDIT] -- A part la plaisanterie, il semble qu'il y ait des moyens de contourner le problème. [EDIT] Malheureusement, la solution que j'ai trouvée n'est plus disponible.

1voto

Biju Kunjummen Points 20258

En complément de votre edit3, pour le rendu JSP, l'appel final est le suivant

RequestDispatcher requestDispatcher = httpRequest.getRequestDispacher(jspPath)
requestDispatcher.forward(httpRequest,httpResponse)

et le RequestDispatcher sont fournies par le conteneur (puisqu'elles dépendent de la manière dont les jsp doivent être compilées, de l'endroit où les jsp compilées doivent être placées, etc.) L'implémentation Mock de RequestDispatcher capture simplement la page JSP transmise, et vous ne peut que valider que le chemin d'accès à la JSP est bien celui que vous attendez.

0voto

Michael Wyraz Points 449

J'ai créé une version modifiée de MockRequestDispatcher qui construit la même chaîne que MockMvc et transmet la demande à cette chaîne pour les demandes de transfert. Cela résout le problème pour moi. Il y a encore du travail à faire si la vue est rendue en dehors du servlet dispatcher (par exemple via jsp).

Le code est là : https://gist.github.com/micw/a8b20720db900762a29d

Le plus gros du travail a consisté à l'injecter au bon endroit. Je l'ai fait en créant un RequestPostProcessor pour MockMvc et en utilisant la magie de mockito qui intercepte l'appel MockHttpServletRequest.getRequestDispatcher.

Pour l'utiliser, ajoutez-le à votre classpath de test et appelez-le directement après avoir créé votre instance MockMvc :

    mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
            [...]
            .build();
    WebMvcRequestDispatcherForwardFix.apply(mvc);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X