Как избежать исключения "кругового пути" с помощью теста Spring MVC

у меня есть следующий код в один из моих контроллеров:

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

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

Я просто пытаюсь проверить его с помощью весенний тест MVC следующим образом:

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

Я получаю следующее исключение:

круговой путь представления [предпочтение]: отправил бы назад к текущему URL-адрес обработчика [/preference] снова. Проверьте настройки ViewResolver! (Намек: Это может быть результатом неопределенного представления из-за представления по умолчанию имя поколение.)

что я нахожу странным, так это то, что он отлично работает, когда я загружаю" полную " контекстную конфигурацию это включает в себя шаблон и вид преобразователей, как показано ниже:

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

Я хорошо знаю, что префикс, добавленный распознавателем шаблонов, гарантирует, что нет" кругового пути просмотра", когда приложение использует этот распознаватель шаблонов.

но тогда как я должен протестировать свое приложение с помощью теста Spring MVC? У кого-нибудь есть ключ?

14 ответов


это не имеет ничего общего с весенним тестированием MVC.

когда вы не объявляете ViewResolver, Spring регистрирует значение по умолчанию InternalResourceViewResolver, который создает экземпляры JstlView на оказание View.

на JstlView класс расширяется InternalResourceView что это

оболочка для JSP или другого ресурса в том же веб-приложении. Предоставляет объекты модели в качестве атрибутов запроса и пересылает запрос к указанному URL ресурса с помощью класса javax.сервлет.Вызов requestdispatcher.

URL для этого представления должен указывать ресурс в интернете приложения, подходящие для передового вызов requestdispatcher или включить метод.

жирным-мое. Другими словами, представление перед рендерингом попытается получить RequestDispatcher, к которому forward(). Перед этим он проверяет следующее

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 - это имя представления, которое вы вернули из @Controller. В этом примере это preference. Переменная uri содержит uri обрабатываемого запроса, который является /context/preference.

код выше понимает, что если вы должны были переслать /context/preference, тот же сервлет (так как тот же обработал предыдущий) будет обрабатывать запрос, и вы войдете в бесконечный цикл.


когда вы объявляете ThymeleafViewResolver и ServletContextTemplateResolver конкретной prefix и suffix создает View по-разному, давая это путь, как

WEB-INF/web-templates/preference.html

ThymeleafView экземпляры найдите файл относительно ServletContext путь с помощью ServletContextResourceResolver

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

который в конце концов

return servletContext.getResourceAsStream(resourceName);

это получает ресурс, который относительно ServletContext путь. Затем он может использовать TemplateEngine для генерации HTML. Здесь не может быть бесконечной петли.


Я решил эту проблему, используя @ResponseBody, как показано ниже:

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

вот как я решил эту проблему:

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

вот простое исправление, если вы на самом деле не заботитесь о рендеринге представления.

создайте подкласс InternalResourceViewResolver, который не проверяет пути кругового обзора:

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

затем настройте свой тест с ним:

MockMvc mockMvc;

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

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

@Controller@RestController

у меня была та же проблема, и я заметил, что мой контроллер также был аннотирован @Controller. Заменив его на @RestController решается вопрос. Вот объяснение из Spring Web MVC:

@RestController включает аннотацию, которая сама мета-аннотацией с @Controller и @ResponseBody, указывающим контроллер, каждый метод наследует аннотацию @ResponseBody уровня типа и поэтому пишет непосредственно к телу ответа vs разрешение и визуализация с HTML-шаблоном.


Если вы используете Spring Boot, добавьте зависимость thymeleaf в свой pom.XML-код:

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

Я использую Spring Boot, чтобы попытаться загрузить веб-страницу, а не тестировать, и у меня была эта проблема. Мое решение было немного иным, чем те, что были выше, учитывая несколько иные обстоятельства. (хотя эти ответы helpled меня понять.)

мне просто пришлось изменить свою зависимость от стартера Spring Boot в Maven от:

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

просто изменение " web " на "thymeleaf" исправило проблему для меня.


Для Thymeleaf:

Я только начал использовать spring 4 и thymeleaf, когда я столкнулся с этой ошибкой, она была решена путем добавления:

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

при использовании @Controller аннотацию, вам нужно @RequestMapping и @ResponseBody Примечание. Повторите попытку после добавления аннотации @ResponseBody


Я использую аннотацию для настройки spring web app, проблема решена путем добавления InternalResourceViewResolver bean для конфигурации. Надеюсь, это будет полезно.

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

это происходит потому, что Spring удаляет " предпочтение "и добавляет" предпочтение " снова, делая тот же путь, что и Uri запроса.

происходит подобное : запрос Uri: "/preference"

удалить "предпочтения": "/ "

добавить путь: "/"+"предпочтение"

конец строки: "/preference"

Это попадает в цикл, который Весна уведомляет вас, бросая исключение.

его лучше в ваших интересах, чтобы дать другое название, как "preferenceView" или как угодно.


Я использую Spring Boot с Thymeleaf. Это то, что сработало для меня. Есть похожие ответы с JSP, но обратите внимание, что я использую HTML, а не JSP, и они находятся в папке src/main/resources/templates как в стандартном проекте Spring Boot, как объяснено здесь. Это тоже может быть твоим делом.

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

надеюсь, что это помогает.


попробуйте добавить compile ("org.springframework.boot: spring-boot-starter-thymeleaf") зависимость от вашего файла gradle.Thymeleaf помогает отображать представления.


еще один простой подход:

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