How to avoid the "Circular view path" exception with Spring MVC test

circular-reference, spring, spring-mvc, spring-mvc-test, thymeleaf

Solution

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.

Problem

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?

Original source