1. 概要

統合テストは、システムのエンドツーエンドの動作を検証することにより、アプリケーション開発サイクルで重要な役割を果たします。

このチュートリアルでは、サーブレットコンテナを明示的に開始せずにコントローラをテストする統合テストを記述して実行するために、SpringMVCテストフレームワークを活用する方法を学習します。

2. 準備

この記事で使用する統合テストを実行するには、いくつかのMaven依存関係が必要です。 何よりもまず、最新の junit-jupiter-engine junit-jupiter-api 、および Springtestの依存関係が必要です。

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.8.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.8.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.3</version>
    <scope>test</scope>
</dependency>

結果を効果的にアサートするために、HamcrestおよびJSONパスも使用します。

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-library</artifactId>
    <version>2.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
    <version>2.5.0</version>
    <scope>test</scope>
</dependency>

3. SpringMVCテスト構成

次に、Spring対応のテストを構成して実行する方法を見てみましょう。

3.1. JUnit5を使用したテストでSpringを有効にする

JUnit 5は、クラスがJUnitテストと統合できる拡張インターフェースを定義します。

この拡張機能を有効にするには、テストクラスに@ExtendWithアノテーションを追加し、loadに拡張機能クラスを指定します。 Springテストを実行するには、SpringExtension.class。を使用します。

また、コンテキスト構成をロードするために @ContextConfigurationアノテーションが必要であり、テストで使用するコンテキストをブートストラップします

みてみましょう:

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { ApplicationConfig.class })
@WebAppConfiguration
public class GreetControllerIntegrationTest {
    ....
}

@ContextConfiguration、で、この特定のテストに必要な構成をロードするApplicationConfig.class構成クラスを提供したことに注意してください。

ここでは、Java構成クラスを使用して、コンテキスト構成を指定します。 同様に、XMLベースの構成を使用できます。

@ContextConfiguration(locations={""})

最後に、 @WebAppConfigurationでテストに注釈を付けます。これにより、Webアプリケーションコンテキストが読み込まれます。

デフォルトでは、パス src / main /webappでルートWebアプリケーションを検索します。value 属性を渡すだけで、この場所をオーバーライドできます。

@WebAppConfiguration(value = "")

3.2. WebApplicationContextオブジェクト

WebApplicationContext は、Webアプリケーション構成を提供します。 すべてのアプリケーションBeanとコントローラーをコンテキストにロードします。

これで、Webアプリケーションコンテキストをテストに直接接続できるようになります。

@Autowired
private WebApplicationContext webApplicationContext;

3.3. WebコンテキストBeanのモック

MockMvc は、Spring MVCテストのサポートを提供します。すべてのWebアプリケーションBeanをカプセル化し、テストに使用できるようにします。

それを使用する方法を見てみましょう:

private MockMvc mockMvc;
@BeforeEach
public void setup() throws Exception {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
}

mockMvcオブジェクトを@BeforeEachアノテーション付きメソッドで初期化するので、すべてのテスト内で初期化する必要はありません。

3.4. テスト構成を確認する

WebApplicationContext オブジェクト( webApplicationContext )が正しくロードされていることを確認しましょう。 また、適切なservletContextがアタッチされていることも確認します。

@Test
public void givenWac_whenServletContext_thenItProvidesGreetController() {
    ServletContext servletContext = webApplicationContext.getServletContext();
    
    Assert.assertNotNull(servletContext);
    Assert.assertTrue(servletContext instanceof MockServletContext);
    Assert.assertNotNull(webApplicationContext.getBean("greetController"));
}

GreetController。javabeanがWebコンテキストに存在することも確認していることに注意してください。 これにより、SpringBeanが正しくロードされます。 この時点で、統合テストのセットアップが完了します。 次に、MockMvcオブジェクトを使用してリソースメソッドをテストする方法を説明します。

4. 統合テストの作成

このセクションでは、テストフレームワークで利用できる基本的な操作について説明します。

パス変数とパラメーターを使用してリクエストを送信する方法を見ていきます。 また、適切なビュー名が解決されたこと、または応答本文が期待どおりであることを表明する方法を示すいくつかの例を示します。

以下に示すスニペットは、M ockMvcRequestBuildersまたはMockMvcResultMatchersクラスからの静的インポートを使用します。

4.1. ビュー名を確認する

テストから/homePage エンドポイントをとして呼び出すことができます。

http://localhost:8080/spring-mvc-test/

また

http://localhost:8080/spring-mvc-test/homePage

まず、テストコードを見てみましょう。

@Test
public void givenHomePageURI_whenMockMVC_thenReturnsIndexJSPViewName() {
    this.mockMvc.perform(get("/homePage")).andDo(print())
      .andExpect(view().name("index"));
}

それを分解しましょう:

  • Perform()メソッドはGETリクエストメソッドを呼び出し、ResultActionsを返します。 この結果を使用して、コンテンツ、HTTPステータス、ヘッダーなど、応答に関するアサーションの期待値を得ることができます。
  • andDo(print())は、要求と応答を出力します。 これは、エラーが発生した場合に詳細を表示するのに役立ちます。
  • andExpect()は、指定された引数を期待します。 この場合、 MockMvcResultMatchers.view()。を介して「インデックス」が返されることを期待しています。

4.2. 応答本文を確認する

テストから/greetエンドポイントを次のように呼び出します。

http://localhost:8080/spring-mvc-test/greet

期待される出力は次のようになります。

{
    "id": 1,
    "message": "Hello World!!!"
}

テストコードを見てみましょう:

@Test
public void givenGreetURI_whenMockMVC_thenVerifyResponse() {
    MvcResult mvcResult = this.mockMvc.perform(get("/greet"))
      .andDo(print()).andExpect(status().isOk())
      .andExpect(jsonPath("$.message").value("Hello World!!!"))
      .andReturn();
    
    Assert.assertEquals("application/json;charset=UTF-8", 
      mvcResult.getResponse().getContentType());
}

何が起こっているのかを正確に見てみましょう:

  • andExpect(MockMvcResultMatchers.status()。isOk())は、応答HTTPステータスが Ok 200)であることを確認します。 これにより、リクエストが正常に実行されました。
  • andExpect(MockMvcResultMatchers.jsonPath( “$。message”)。value( “Hello World !!!”))は、応答の内容が引数「 HelloWorld !!!」ここでは、 jsonPath を使用しました。これは、応答コンテンツを抽出し、要求された値を提供します。
  • andReturn()は、 MvcResult オブジェクトを返します。これは、ライブラリで直接達成できないものを検証する必要がある場合に使用されます。 この場合、 MvcResult オブジェクトから抽出された応答のコンテンツタイプに一致するように、assertEqualsを追加しました。

4.3. パス変数を使用してGETリクエストを送信する

テストから/greetWithPathVariable/{name}エンドポイントを次のように呼び出します。

http://localhost:8080/spring-mvc-test/greetWithPathVariable/John

期待される出力は次のようになります。

{
    "id": 1,
    "message": "Hello World John!!!"
}

テストコードを見てみましょう:

@Test
public void givenGreetURIWithPathVariable_whenMockMVC_thenResponseOK() {
    this.mockMvc
      .perform(get("/greetWithPathVariable/{name}", "John"))
      .andDo(print()).andExpect(status().isOk())
      
      .andExpect(content().contentType("application/json;charset=UTF-8"))
      .andExpect(jsonPath("$.message").value("Hello World John!!!"));
}

MockMvcRequestBuilders.get(“ /greetWithPathVariable/ {name}”、“ John”)は、リクエストを“ /greetWithPathVariable/John.”として送信します。

これは、読みやすさ、およびURLに動的に設定されているパラメーターを知ることに関してより簡単になります。 必要な数のパスパラメータを渡すことができることに注意してください。

4.4. クエリパラメータを使用してGETリクエストを送信する

テストから/greetWithQueryVariable?name={name}エンドポイントを次のように呼び出します。

http://localhost:8080/spring-mvc-test/greetWithQueryVariable?name=John%20Doe

この場合、期待される出力は次のようになります。

{
    "id": 1,
    "message": "Hello World John Doe!!!"
}

それでは、テストコードを見てみましょう。

@Test
public void givenGreetURIWithQueryParameter_whenMockMVC_thenResponseOK() {
    this.mockMvc.perform(get("/greetWithQueryVariable")
      .param("name", "John Doe")).andDo(print()).andExpect(status().isOk())
      .andExpect(content().contentType("application/json;charset=UTF-8"))
      .andExpect(jsonPath("$.message").value("Hello World John Doe!!!"));
}

param( “name”、 “John Doe”)は、GETリクエストにクエリパラメータを追加します。 これは「/greetWithQueryVariable?name=John%20Doe。

クエリパラメータは、URIテンプレートスタイルを使用して実装することもできます。

this.mockMvc.perform(
  get("/greetWithQueryVariable?name={name}", "John Doe"));

4.5. POSTリクエストを送信する

テストから/greetWithPostエンドポイントを次のように呼び出します。

http://localhost:8080/spring-mvc-test/greetWithPost

出力を取得する必要があります。

{
    "id": 1,
    "message": "Hello World!!!"
}

そして、私たちのテストコードは次のとおりです。

@Test
public void givenGreetURIWithPost_whenMockMVC_thenVerifyResponse() {
    this.mockMvc.perform(post("/greetWithPost")).andDo(print())
      .andExpect(status().isOk()).andExpect(content()
      .contentType("application/json;charset=UTF-8"))
      .andExpect(jsonPath("$.message").value("Hello World!!!"));
}

MockMvcRequestBuilders.post(“ /greetWithPost”)はPOSTリクエストを送信します。 パス変数とクエリパラメータは以前と同様の方法で設定できますが、フォームデータは param()メソッドを介してのみ設定できます。クエリパラメータは次のようになります。

http://localhost:8080/spring-mvc-test/greetWithPostAndFormData

その場合、データは次のようになります。

id=1;name=John%20Doe

したがって、次のようになります。

{
    "id": 1,
    "message": "Hello World John Doe!!!"
}

私たちのテストを見てみましょう:

@Test
public void givenGreetURI_whenMockMVC_thenVerifyResponse() throws Exception {
    MvcResult mvcResult = this.mockMvc.perform(MockMvcRequestBuilders.get("/greet"))
      .andDo(print())
      .andExpect(MockMvcResultMatchers.status().isOk())
      .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("Hello World!!!"))
      .andReturn();
 
   assertEquals("application/json;charset=UTF-8", mvcResult.getResponse().getContentType());
}

上記のコードスニペットでは、 id を「1」として、nameを「JohnDoe」として2つのパラメーターを追加しました。

5. MockMvcの制限

MockMvc は、Webエンドポイントを呼び出し、同時にそれらの応答を検査およびアサートするための、エレガントで使いやすいAPIを提供します。 そのすべての利点にもかかわらず、いくつかの制限があります。

まず、DispatcherServletのサブクラスを使用してテスト要求を処理します。 具体的には、 TestDispatcherServlet は、コントローラーを呼び出し、使い慣れたSpringの魔法をすべて実行します。

MockMvcクラスはこのTestDispatcherServletを内部でラップします。 したがって、 Perform()メソッドを使用してリクエストを送信するたびに、MockMvcは基盤となるTestDispatcherServletを直接使用します。 したがって、実際のネットワーク接続は確立されていないため、MockMvcを使用している間はネットワークスタック全体をテストしません。

また、はHTTPリクエストとレスポンスをモックするために偽のWebアプリケーションコンテキストを準備するため、本格的なSpringアプリケーションのすべての機能をサポートしていない可能性があります。

たとえば、このモックセットアップはHTTPリダイレクトをサポートしていません。 これは最初はそれほど重要ではないように思われるかもしれません。 ただし、Spring Bootは、現在の要求を / error エンドポイントにリダイレクトすることにより、いくつかのエラーを処理します。 したがって、 MockMvcを使用している場合、一部のAPI障害をテストできない可能性があります。

MockMvcの代わりに、より実際のアプリケーションコンテキスト を設定してから、 RestTemplate、、さらにはREST-assuredを使用できます。アプリケーションをテストします。

たとえば、これはSpringBootを使用すると簡単です。

@SpringBootTest(webEnvironment = DEFINED_PORT)
public class GreetControllerRealIntegrationTest {

    @Before
    public void setUp() {
        RestAssured.port = DEFAULT_PORT;
    }

    @Test
    public void givenGreetURI_whenSendingReq_thenVerifyResponse() {
        given().get("/greet")
          .then()
          .statusCode(200);
    }
}

ここでは、 @ExtendWith(SpringExtension.class)を追加する必要はありません。

このように、すべてのテストは、ランダムなTCPポートでリッスンするアプリケーションに対して実際のHTTPリクエストを作成します。

6. 結論

この記事では、いくつかの簡単なSpring対応の統合テストを実装しました。

また、WebApplicationContextおよびMockMvcオブジェクトの作成についても説明しました。これらは、アプリケーションのエンドポイントを呼び出す際に重要な役割を果たします。

さらに見て、パラメータの受け渡しのバリエーションを使用してGETおよびPOSTリクエストを送信する方法、およびHTTP応答のステータス、ヘッダー、およびコンテンツを確認する方法について説明しました。

次に、 MockMvcのいくつかの制限を評価しました。これらの制限を知っていると、テストの実装方法について十分な情報に基づいた決定を下すことができます。

最後に、これらすべての例とコードスニペットの実装は、GitHub利用できます。