5

ログイン機能をカバーするためのテストをしようとしています。Springのバージョンは3.2.12です。次のように宣言されたセッション Bean があります。

@Service
@Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
public class ClientSessionServiceImpl implements ClientSessionService {
    @Autowired
    private HttpServletRequest request;
    // This method is called during the login routine from the filter
    public boolean checkUser() {
    // I rely on request attributes here, which were set in the filter
    }

これはサーバー上で実行すると完全に機能しますが、スプリングテストの手段で実行すると問題が発生します。これは私のテスト方法です:

this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).addFilter(springSecurityFilterChain).build();
mockMvc.perform(post(URL));

デバッグ後、テスト スプリング コンテキストが開始されると、ServletTestExecutionListener.setUpRequestContextIfNecessary で MockHttpServletRequest のインスタンスが作成されることがわかりました。 MockHttpServletRequest request = new MockHttpServletRequest(mockServletContext); // このインスタンスを A と呼びましょう。そして、これはどこにでも注入されるインスタンスです。

@Autowired
HttpServletRequest request;

一方、MockMvc.perform を呼び出すと、MockHttpServletRequest の別のインスタンス (インスタンス B と呼びましょう) が作成され、すべてのフィルター、サーブレットなどに渡されます。つまり、基本的に、要求のフィルターに設定した属性はできません。 MockHttpServletRequest の別のインスタンスがそこに挿入されるため、ClientSessionServiceImpl で読み取られます。

私はこれに多くの時間を費やしましたが、まだ解決策を見つけていません。

PS StackOverflowで検索しました。同様のタイトルの質問がありますが、HttpServletRequestをパラメーターとして渡したくなく、正当な理由がない限り、自動配線することを好むため、私とは異なる問題を説明していますそれ。

4

4 に答える 4

2

したがって、基本的に、リクエストのフィルターに設定した属性は、ClientSessionServiceImpl で読み取ることができません。これは、MockHttpServletRequest の別のインスタンスがそこに挿入されるためです。

RequestAttributesこれは、Springが に取り込まれるタイミングに関するタイミングの問題RequestContextHolderです。RequestContextFilter本番環境では、またはのいずれかを構成していると想定しますRequestContextListener

いずれにせよ、RequestContextFilterテストのフィルタ チェーンの先頭に のインスタンスを手動で追加すると、問題が解決します。

mockMvc = MockMvcBuilders
  .webAppContextSetup(this.wac)
  .addFilters(new RequestContextFilter(), testFilterChain)
  .build();

これが Spring Framework 4.2 のデフォルトの動作になることに注意してください: シミュレートするコードRequestContextFilterは に直接実装されMockMvcます。詳細については、JIRA の問題SPR-13217を参照してください。


余談MockHttpServletRequestですが、 によって作成された の設定ServletTestExecutionListenerはサポートされていません。使用しMockMvcている場合は、RequestBuilders.

ServletTestExecutionListenerただし、手動で作成したモック リクエストを変更し、それを で再利用する具体的な必要がある場合はMockMvc、プロジェクトに次のクラスを作成できます。

package org.springframework.test.web.servlet.request;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;

import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * Patched version of {@link MockHttpServletRequestBuilder}.
 *
 * @author Sam Brannen
 * @since 4.2
 */
public class PatchedMockHttpServletRequestBuilder extends MockHttpServletRequestBuilder {

    public static MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables) {
        return new PatchedMockHttpServletRequestBuilder(HttpMethod.GET, urlTemplate, urlVariables);
    }

    public PatchedMockHttpServletRequestBuilder(HttpMethod httpMethod, String urlTemplate, Object... urlVariables) {
        super(httpMethod, urlTemplate, urlVariables);
    }

    /**
     * Create a {@link MockHttpServletRequest}.
     * <p>If an instance of {@code MockHttpServletRequest} is available via
     * the {@link RequestAttributes} bound to the current thread in
     * {@link RequestContextHolder}, this method simply returns that instance.
     * <p>Otherwise, this method creates a new {@code MockHttpServletRequest}
     * based on the supplied {@link ServletContext}.
     * <p>Can be overridden in subclasses.
     * @see RequestContextHolder#getRequestAttributes()
     * @see ServletRequestAttributes
     */
    @Override
    protected MockHttpServletRequest createServletRequest(ServletContext servletContext) {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes instanceof ServletRequestAttributes) {
            HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
            if (request instanceof MockHttpServletRequest) {
                return (MockHttpServletRequest) request;
            }
        }

        return new MockHttpServletRequest(servletContext);
    }

}

注:パッケージに入っている必要があります。org.springframework.test.web.servlet.requestそうしないと、MockHttpServletRequestBuilder必要な拡張ができません。

次に、 fromの代わりに fromget()メソッドを使用すると、すべてが期待どおりに機能するはずです!PatchedMockHttpServletRequestBuilderMockMvcRequestBuilders

明らかに、上記の例はを再実装 get()していますが、当然post()、 などに対しても同じことができます。

参考までに: 上記のパッチを適用したバージョンを最終的createServletRequest()に Spring Framework 4.2.x にコミットする可能性があります (JIRA issue SPR-13211を参照)。

よろしく、

Sam ( Spring TestContext フレームワークの作成者)

于 2015-07-07T16:48:34.230 に答える
1

残りのコントローラーで HttpServletRequest と HttpServletResponse の両方が自動配線されているという同様の問題がありました

@Autowired protected HttpServletRequest httpRequest;
@Autowired protected HttpServletResponse httpResponse;

ただし、以下の構成を使用してスプリング テストを使用しようとすると、httpRequestテスト スコープが原因で自動配線できないため、テストに失敗しました。

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("classpath:spring-config-unit-test.xml")

Webを検索した後のソリューションは、以下のように、モックのリクエストとレスポンスをBean定義(単体テスト) xmlの最初の行として宣言しました。これが誰かを助けることを願っています

<bean class="org.springframework.mock.web.MockHttpServletRequest" name="httpRequest" lazy-init="false" />
<bean class="org.springframework.mock.web.MockHttpServletResponse" name="httpResponse" lazy-init="false" />
于 2015-08-28T16:16:15.267 に答える
0

RequestPostProcessorを使用して、実行内で作成されたリクエストを交換できます。次のような util メソッドをテストに追加できます。

private static RequestPostProcessor mockedRequest(final MockHttpServletRequest mockHttpServletRequest) {
    return new RequestPostProcessor() {
        @Override
        public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
            return mockHttpServletRequest;
        }
    };
}

withメソッドを介して追加することにより、このポストプロセッサを適用します

    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).addFilter(springSecurityFilterChain).build();
    mockMvc.perform(post(URL).with(mockedRequest(request)));

ここでメソッドにrequest渡すとmockedRequest、必要な属性を保持する元のリクエストになります

于 2015-06-10T14:52:52.577 に答える