Question

I have the following code in one of my controllers:

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

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

I am simply trying to test it using Spring MVC test as follows:

@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());
    }
}

I am getting the following exception:

Circular view path [preference]: would dispatch back to the current handler URL [/preference] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)

What I find strange is that it works fine when I load the "full" context configuration that includes the template and view resolvers as shown below:

<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>

I am well aware that the prefix added by the template resolver ensures that there is not "circular view path" when the app uses this template resolver.

But then how I am supposed to test my app using Spring MVC test?

Was it helpful?

Solution 3

This has nothing to do with Spring MVC testing.

When you don't declare a ViewResolver, Spring registers a default InternalResourceViewResolver which creates instances of JstlView for rendering the View.

The JstlView class extends InternalResourceView which is

Wrapper for a JSP or other resource within the same web application. Exposes model objects as request attributes and forwards the request to the specified resource URL using a javax.servlet.RequestDispatcher.

A URL for this view is supposed to specify a resource within the web application, suitable for RequestDispatcher's forward or include method.

Emphasis mine. In other words, the view, before rendering, will try to get a RequestDispatcher to which to forward(). Before doing this it checks the following

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.)");
}

where path is the view name, what you returned from the @Controller. In this example, that is preference. The variable uri holds the uri of the request being handled, which is /context/preference.

The code above realizes that if you were to forward to /context/preference, the same servlet (since the same handled the previous) would handle the request and you would go into an endless loop.


When you declare a ThymeleafViewResolver and a ServletContextTemplateResolver with a specific prefix and suffix, it builds the View differently, giving it a path like

WEB-INF/web-templates/preference.html

ThymeleafView instances locate the file relative to the ServletContext path by using a ServletContextResourceResolver

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

which eventually

return servletContext.getResourceAsStream(resourceName);

This gets a resource that is relative to the ServletContext path. It can then use the TemplateEngine to generate the HTML. There's no way an endless loop can happen here.

OTHER TIPS

@Controller@RestController

I had the same issue and I noticed that my controller was also annotated with @Controller. Replacing it with @RestController solved the issue. Here is the explanation from Spring Web MVC:

@RestController is a composed annotation that is itself meta-annotated with @Controller and @ResponseBody indicating a controller whose every method inherits the type-level @ResponseBody annotation and therefore writes directly to the response body vs view resolution and rendering with an HTML template.

I solved this problem by using @ResponseBody like below:

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

This is how I solved this problem:

@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();
    }

Also you can make bean for this in .xml file

<bean id = "viewResolver" class = "org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/view/"/>
    <property name="suffix" value=".jsp"/>
</bean

I am using Spring Boot to try and load a webpage, not test, and had this problem. My solution was a bit different than those above considering the slightly different circumstances. (although those answers helpled me understand.)

I simply had to change my Spring Boot starter dependency in Maven from:

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

to:

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

Just changing the 'web' to 'thymeleaf' fixed the problem for me.

Here's an easy fix if you don't actually care about rendering the view.

Create a subclass of InternalResourceViewResolver which doesn't check for circular view paths:

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;
    }
}

Then set up your test with it:

MockMvc mockMvc;

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

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

If you are using Spring Boot, then add thymeleaf dependency into your pom.xml:

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

if you have not used a @RequestBody and are using only @Controller, simplest way to fix this is using @RestController instead of @Controller

Adding / after /preference solved the problem for me:

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

In my case, I was trying out Kotlin + Spring boot and I got into the Circular View Path issue. All the suggestions I got online could not help, until I tried the below:

Originally I had annotated my controller using @Controller

import org.springframework.stereotype.Controller

I then replaced @Controller with @RestController

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

And it worked.

I am using Spring Boot with Thymeleaf. This is what worked for me. There are similar answers with JSP but note that I am using HTML, not JSP, and these are in the folder src/main/resources/templates like in a standard Spring Boot project as explained here. This could also be your case.

@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;
}

Hope this helps.

Add the annotation @ResponseBody to your method return.

When running Spring Boot + Freemarker if the page appears:

Whitelabel Error Page This application has no explicit mapping for / error, so you are seeing this as a fallback.

In spring-boot-starter-parent 2.2.1.RELEASE version freemarker does not work:

  1. rename Freemarker files from .ftl to .ftlh
  2. Add to application.properties: spring.freemarker.expose-request-attributes = true

spring.freemarker.suffix = .ftl

For Thymeleaf:

I just began using spring 4 and thymeleaf, when I encountered this error it was resolved by adding:

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

When using @Controller annotation, you need @RequestMapping and @ResponseBody annotations. Try again after adding annotation @ResponseBody

I use the annotation to configure spring web app, the problem solved by adding a InternalResourceViewResolver bean to the configuration. Hope it would be helpful.

@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;
    }
}

This is happening because Spring is removing "preference" and appending the "preference" again making the same path as the request Uri.

Happening like this : request Uri: "/preference"

remove "preference": "/"

append path: "/"+"preference"

end string: "/preference"

This is getting into a loop which the Spring notifies you by throwing exception.

Its best in your interest to give a different view name like "preferenceView" or anything you like.

try adding compile("org.springframework.boot:spring-boot-starter-thymeleaf") dependency to your gradle file.Thymeleaf helps mapping views.

In my case, I had this problem while trying to serve JSP pages using Spring boot application.

Here's what worked for me:

application.properties

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

pom.xml

To enable support for JSPs, we would need to add a dependency on tomcat-embed-jasper.

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>provided</scope>
</dependency>

In my case circular view path in spring boot 2 and jdk 11 was fixed by redirecting to index.html:

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            }
            @Override
            public void addViewControllers(ViewControllerRegistry registry) {
                registry.addViewController("/").setViewName("redirect:/index.html");
            }
        };

Add a view resovler in xml file

<bean id = "viewResolver" class = "org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/view/"/>
    <property name="suffix" value=".jsp"/>
</bean

Another simple approach:

package org.yourpackagename;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

      @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
            return application.sources(PreferenceController.class);
        }


    public static void main(String[] args) {
        SpringApplication.run(PreferenceController.class, args);
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top