web-dev-qa-db-ja.com

Spring MVCテストで「円形ビューパス」例外を回避する方法

コントローラーの1つに次のコードがあります。

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

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

次のようにSpring MVC testを使用してテストしようとしています。

@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 [/設定]に再度ディスパッチします。 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テストを使用してアプリをどのようにテストするのですか?誰にも手がかりがありますか?

88
balteo

これは、Spring MVCテストとは関係ありません。

ViewResolverを宣言しない場合、SpringはInternalResourceViewResolverをレンダリングするためのJstlViewのインスタンスを作成するデフォルトのViewを登録します。

JstlViewクラスはInternalResourceViewを拡張します。

同じWebアプリケーション内のJSPまたはその他のリソースのラッパー。モデルオブジェクトをリクエスト属性として公開し、javax.servlet.RequestDispatcherを使用して指定されたリソースURLにリクエストを転送します。

このビューのURLは、RequestDispatcherのforwardメソッドまたはincludeメソッドに適したWebアプリケーション内のリソースを指定することになっています。

太字は私のものです。つまり、ビューは、レンダリングの前に、forward()へのRequestDispatcherを取得しようとします。これを行う前に、以下をチェックします

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を生成できます。ここで無限ループが発生することはありません。

55

以下のように@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) {
81
Deepti Kohli

@Controller@RestController

私は同じ問題を抱えていて、コントローラーにも@Controllerという注釈が付けられていることに気付きました。この問題を@RestControllerに置き換えることで解決しました。 Spring Web MVC の説明を次に示します。

@RestControllerは、それ自体が@Controllerおよび@ResponseBodyでメタアノテーションされた合成アノテーションであり、すべてのメソッドがタイプレベルの@ResponseBodyアノテーションを継承するコントローラーを示すため、応答テンプレートとビューの解像度に直接書き込み、HTMLテンプレートでレンダリングします。

35
Boris

これは私がこの問題を解決した方法です:

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

ビューのレンダリングを実際に気にしない場合の簡単な修正方法を次に示します。

循環ビューパスをチェックしない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();
}
13
Dave Bower

Spring Bootを使用している場合、thymleaf依存関係をpom.xmlに追加します。

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

私はSpring Bootを使用してテストではなくWebページをロードしようとしましたが、この問題がありました。私の解決策は、わずかに異なる状況を考慮して、上記のものとは少し異なっていました。 (それらの答えは私を理解するのに役立ちましたが。)

MavenのSpring Bootスターターの依存関係を次のように変更する必要がありました。

<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」に変更するだけで問題が解決しました。

10
Old Schooled

ThymeleafでSpring Bootを使用しています。これは私のために働いたものです。 JSPについても同様の回答がありますが、JSPではなくHTMLを使用していることに注意してください。これらは、説明したように標準のSpring Bootプロジェクトのようなフォルダーsrc/main/resources/templatesにあります here 。これもあなたのケースかもしれません。

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

お役に立てれば。

3
Pedro Lopez

Thymeleafの場合:

Spring 4とthymeleafの使用を開始しましたが、このエラーが発生したとき、次のコードを追加することで解決しました。

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

/の後に/preferenceを追加すると、問題が解決しました。

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

@Controller注釈を使用する場合は、@RequestMappingおよび@ResponseBody注釈が必要です。注釈を追加してからもう一度お試しください@ResponseBody

1
Gowri Ayyanar

私の場合、Kotlin + Springブートを試していましたが、Circular View Pathの問題に遭遇しました。私がオンラインで得た提案はすべて、以下を試してみるまで助けられませんでした。

元々、@Controllerを使用してコントローラーに注釈を付けていました

import org.springframework.stereotype.Controller

次に、@Controller@RestControllerに置き換えました

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

そしてそれは働いた。

1
johnmilimo

compile( "org.springframework.boot:spring-boot-starter-thymeleaf")依存関係をgradleファイルに追加してみてください。Thymeleafはビューのマッピングに役立ちます。

0
aishwarya kore

これは、Springが「設定」を削除し、「設定」を再度追加して、リクエストUriと同じパスを作成しているために発生しています。

次のように動作します:Uriをリクエスト: "/ preference"

「設定」を削除:「/」

パスを追加: "/" + "preference"

終了文字列: "/ preference"

これは、Springが例外をスローすることで通知するループに入ります。

「preferenceView」などの別のビュー名を付けるのがあなたの興味の中で一番です。

0
xpioneer

このアノテーションを使用して、Spring Webアプリを構成します。この問題は、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;
    }
}
0
alijandro