web-dev-qa-db-fra.com

Comment éviter l'exception "Circular view path" avec le test Spring MVC

J'ai le code suivant dans l'un de mes contrôleurs:

@Controller
@RequestMapping("/preference")
public class PreferenceController {

    @RequestMapping(method = RequestMethod.GET, produces = "text/html")
    public String preference() {
        return "preference";
    }
}

J'essaye simplement de le tester en utilisant Spring MVC test comme suit:

@ContextConfiguration
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class PreferenceControllerTest {

    @Autowired
    private WebApplicationContext ctx;

    private MockMvc mockMvc;
    @Before
    public void setup() {
        mockMvc = webAppContextSetup(ctx).build();
    }

    @Test
    public void circularViewPathIssue() throws Exception {
        mockMvc.perform(get("/preference"))
               .andDo(print());
    }
}

Je reçois l'exception suivante:

Chemin de vue circulaire [préférence]: renverrait au courant URL du gestionnaire [/ préférence] à nouveau. Vérifiez votre configuration ViewResolver! (Conseil: Cela peut être le résultat d'une vue non spécifiée, en raison de la vue par défaut Nom génération.)

Ce que je trouve étrange, c’est que cela fonctionne bien lorsque je charge la configuration de contexte "complète" qui inclut le modèle et les résolveurs de vues comme indiqué ci-dessous:

<bean class="org.thymeleaf.templateresolver.ServletContextTemplateResolver" id="webTemplateResolver">
    <property name="prefix" value="WEB-INF/web-templates/" />
    <property name="suffix" value=".html" />
    <property name="templateMode" value="HTML5" />
    <property name="characterEncoding" value="UTF-8" />
    <property name="order" value="2" />
    <property name="cacheable" value="false" />
</bean>

Je suis bien conscient que le préfixe ajouté par le résolveur de modèle garantit qu'il n'y a pas de "chemin de vue circulaire" lorsque l'application utilise ce résolveur de modèle. 

Mais comment puis-je tester mon application à l'aide du test Spring MVC? Quelqu'un a-t-il un indice?

76
balteo

Cela n'a rien à voir avec les tests Spring MVC.

Lorsque vous ne déclarez pas ViewResolver, Spring enregistre une valeur par défaut InternalResourceViewResolver qui crée des instances de JstlView pour le rendu de View.

La classe JstlView étend InternalResourceView qui est

Wrapper pour un JSP ou une autre ressource au sein de la même application Web . Expose les objets de modèle en tant qu'attributs de requête et transmet la requête à l'URL de la ressource spécifiée à l'aide de javax.servlet.RequestDispatcher.

Une URL pour cette vue est supposée spécifier une ressource sur le Web application, appropriée pour RequestDispatcher's forward ou include méthode.

Bold est à moi. En d'autres termes, la vue, avant le rendu, essaiera d'obtenir un RequestDispatcher auquel forward(). Avant de faire cela, il vérifie ce qui suit

if (path.startsWith("/") ? uri.equals(path) : uri.equals(StringUtils.applyRelativePath(uri, path))) {
    throw new ServletException("Circular view path [" + path + "]: would dispatch back " +
                        "to the current handler URL [" + uri + "] again. Check your ViewResolver setup! " +
                        "(Hint: This may be the result of an unspecified view, due to default view name generation.)");
}

path est le nom de la vue, ce que vous avez renvoyé du @Controller. Dans cet exemple, il s'agit de preference. La variable uri contient l'URI de la demande en cours de traitement, qui est /context/preference

Le code ci-dessus réalise que si vous deviez transmettre à /context/preference, le même servlet (puisque le même manipulé le précédent) traiterait la demande et vous iriez dans une boucle sans fin.


Lorsque vous déclarez un ThymeleafViewResolver et un ServletContextTemplateResolver avec un prefix et un suffix spécifiques, il construit le View différemment, en lui donnant un chemin tel que

WEB-INF/web-templates/preference.html

Les instances ThymeleafView localisent le fichier par rapport au chemin ServletContext à l'aide de la variable ServletContextResourceResolver.

templateInputStream = resourceResolver.getResourceAsStream(templateProcessingParameters, resourceName);`

qui finalement

return servletContext.getResourceAsStream(resourceName);

Cela obtient une ressource qui est relative au chemin ServletContext. Il peut ensuite utiliser TemplateEngine pour générer le code HTML. Il est impossible qu'une boucle sans fin se produise ici.

51

J'ai résolu ce problème en utilisant @ResponseBody comme ci-dessous:

@RequestMapping(value = "/resturl", method = RequestMethod.GET, produces = {"application/json"})
    @ResponseStatus(HttpStatus.OK)
    @Transactional(value = "jpaTransactionManager")
    public @ResponseBody List<DomainObject> findByResourceID(@PathParam("resourceID") String resourceID) {
74
Deepti Kohli

Voici comment j'ai résolu ce problème:

@Before
    public void setup() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/jsp/view/");
        viewResolver.setSuffix(".jsp");

        mockMvc = MockMvcBuilders.standaloneSetup(new HelpController())
                                 .setViewResolvers(viewResolver)
                                 .build();
    }
30
Piotr Sagalara

@Controller@RestController

J'avais le même problème et j'ai remarqué que mon contrôleur était également annoté avec @Controller. Le remplacer par @RestController a résolu le problème. Voici l'explication de Spring Web MVC :

@RestController est une annotation composée qui est elle-même méta-annotée avec @Controller et @ResponseBody indiquant un contrôleur dont chaque Cette méthode hérite de l'annotation @ResponseBody de niveau type et, par conséquent, de écrit directement dans le corps de la réponse par rapport à la résolution de vue et au rendu avec un modèle HTML.

20
Boris

Voici une solution facile si vous ne vous souciez pas vraiment de rendre la vue.

Créez une sous-classe de InternalResourceViewResolver qui ne vérifie pas les chemins de vue circulaire:

public class StandaloneMvcTestViewResolver extends InternalResourceViewResolver {

    public StandaloneMvcTestViewResolver() {
        super();
    }

    @Override
    protected AbstractUrlBasedView buildView(final String viewName) throws Exception {
        final InternalResourceView view = (InternalResourceView) super.buildView(viewName);
        // prevent checking for circular view paths
        view.setPreventDispatchLoop(false);
        return view;
    }
}

Ensuite, configurez votre test avec:

MockMvc mockMvc;

@Before
public void setUp() {
    final MyController controller = new MyController();

    mockMvc =
            MockMvcBuilders.standaloneSetup(controller)
                    .setViewResolvers(new StandaloneMvcTestViewResolver())
                    .build();
}
13
Dave Bower

Si vous utilisez Spring Boot, ajoutez une dépendance thymeleaf dans votre pom.xml: 

    <dependency>
        <groupId>org.thymeleaf</groupId>
        <artifactId>thymeleaf-spring4</artifactId>
        <version>2.1.6.RELEASE</version>
    </dependency>
9
Sarvar Nishonboev

J'utilise Spring Boot pour essayer de charger une page Web, pas pour tester, et j'ai eu ce problème. Ma solution était un peu différente de celles présentées ci-dessus, compte tenu des circonstances légèrement différentes. (bien que ces réponses m'ont aidé à comprendre.) 

Je devais simplement changer ma dépendance du démarreur Spring Boot dans Maven De:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
</dependency>

à:

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Changer simplement le 'web' en 'thymeleaf' a corrigé le problème pour moi. 

7
Old Schooled

Pour Thymeleaf:

J'ai juste commencé à utiliser Spring 4 et thymeleaf. Lorsque j'ai rencontré cette erreur, elle a été résolue en ajoutant:

<bean class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
  <property name="templateEngine" ref="templateEngine" />
  <property name="order" value="0" />
</bean> 
2

Lorsque vous utilisez l'annotation @Controller, vous avez besoin des annotations @RequestMapping et @ResponseBody. Essayez de nouveau après avoir ajouté l'annotation @ResponseBody

1
Gowri Ayyanar

J'utilise Spring Boot avec Thymeleaf. C'est ce qui a fonctionné pour moi. Il existe des réponses similaires avec JSP mais notez que j'utilise HTML, et non JSP, et que celles-ci se trouvent dans le dossier src/main/resources/templates, comme dans un projet Spring Boot standard, comme expliqué here . Cela pourrait aussi être votre cas.

@InjectMocks
private MyController myController;

@Before
public void setup()
{
    MockitoAnnotations.initMocks(this);

    this.mockMvc = MockMvcBuilders.standaloneSetup(myController)
                    .setViewResolvers(viewResolver())
                    .build();
}

private ViewResolver viewResolver()
{
    InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();

    viewResolver.setPrefix("classpath:templates/");
    viewResolver.setSuffix(".html");

    return viewResolver;
}

J'espère que cela t'aides.

1
Pedro Lopez

Dans mon cas, j'essayais la botte Kotlin + Spring et je suis entré dans le problème de la vue circulaire. Toutes les suggestions que j'ai eues en ligne ne pourraient pas m'aider, jusqu'à ce que j'aie essayé ce qui suit:

A l'origine, j'avais annoté mon contrôleur en utilisant @Controller

import org.springframework.stereotype.Controller

J'ai ensuite remplacé @Controller par @RestController

import org.springframework.web.bind.annotation.RestController

Et ça a fonctionné.

1
johnmilimo

essayez d’ajouter une dépendance compile ("org.springframework.boot: spring-boot-starter-thymeleaf") à votre fichier de classement. Thymeleaf aide à mapper les vues.

0
aishwarya kore

Ceci est dû au fait que Spring supprime la "préférence" et l'ajoute à nouveau, créant ainsi le même chemin que la requête Uri. 

Cela se passe comme ceci: Request Uri: "/ Preference"

supprimer "préférence": "/"

append path: "/" + "préférence"

chaîne de fin: "/ préférence"

Cela entre dans une boucle que le ressort vous notifie en lançant une exception. 

Il est de votre intérêt de donner un nom de vue différent tel que "préférence" ou ce que vous préférez.

0
xpioneer

Ajouter / après /preference a résolu le problème pour moi:

@Test
public void circularViewPathIssue() throws Exception {
    mockMvc.perform(get("/preference/"))
           .andDo(print());
}
0

J'utilise l'annotation pour configurer l'application Web Spring, le problème a été résolu en ajoutant un bean InternalResourceViewResolver à la configuration. J'espère que ce serait utile.

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.example.springmvc" })
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Bean
    public InternalResourceViewResolver internalResourceViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/jsp/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
}
0
alijandro