0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

目的

APIテストを自動化したいなーという思いからテストツールを調べていたので、その流れで調査とかPoCをやってきた過程をメモがてら残していきます。
この記事では基礎編として下記を取り扱います。

  • REST Assuredを使ったシンプルなGETメソッドのテスト
  • リクエストを別ファイルで作成したPOSTメソッドのテスト
  • DBをJUnitでつないでAPIのレスポンスとSQLクエリとのレスポンスを比較する

REST Assuredとは

詳細は公式見ていただくとしてざっくり言ってしまえばREST APIのテストをできるJavaに組み込めるツールです。
なので、Intellijからテストを実行できたり作ったりするのが容易です。

公式サイトもあり分かりやすいことが書いてあるので詳細はこちらから。
https://rest-assured.io/

実行環境

  • Windows 11
  • Java 17
  • REST Assured 5.5.0
  • Junit 5.10.3
  • Spring boot 3.3.4

Mavenを使っているのでは下記を設定しています。

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>

	<dependency>
		<groupId>io.rest-assured</groupId>
		<artifactId>rest-assured</artifactId>
		<version>5.5.0</version>
		<scope>test</scope>
	</dependency>
</dependencies>

GETメソッドのテスト

愚直に書いてみよう

REST Assuredではgiven/when/thenの形式でテストを記述することができます。

ここではこういう感じで使います。

  • given
    • ホストネームの指定
    • headerの指定
  • when
    • エンドポイントの呼び出し
  • then
    • テスト結果の比較
package testcase;

import client.ApiClient;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;

public class Step1_FieldAssertionTest {

    @Test
    void should_match_nested_fields() {
        String path = "http://localhost:8080/users/1";

        given()
            .contentType("application/json")
            .accept("application/json")
        .when()
            .get(path)
        .then()
            .log().ifValidationFails()
            .statusCode(200) 
            .body("userId", equalTo(1))
            .body("firstName", equalTo("yamada"))
            .body("lastName", equalTo("taro"));
    }
}

whenget(path)がget methodへのりくえすとを粟原している形です。メソッドごとにあるのは非常にわかりやすいなと思います。

では、テストを実行します。
まずはアプリケーションを立ち上げます。
image.png

テストを実行します。
image.png

「1件のテストが合格しました」と出ているので、これでテストが通ったことが確認できました。
image.png

拡張性を持たせてみよう

上記の形で書いていくとテストケースが増えてくると2つの問題が出てきます。

  • givenで設定するヘッダー情報が重複する -> DRY原則に反する
  • host名がべた書きされているので、環境別の変更に弱い

そこで、これらの問題を解決するApiClientクラスを作ります。

package client;

import io.restassured.RestAssured;
import io.restassured.config.LogConfig;
import io.restassured.specification.RequestSpecification;

import static io.restassured.RestAssured.given;

public class ApiClient {

    // BASE_URIを設定することでhost名を共通で扱えます。
    private static String baseUrl() {
        String env = System.getenv("BASE_URL");
        return (env == null || env.isBlank()) ? "http://localhost:8080" : env;
    }

    static {
        RestAssured.baseURI = baseUrl();
        RestAssured.config = RestAssured.config()
                .logConfig(LogConfig.logConfig().enableLoggingOfRequestAndResponseIfValidationFails());
    }

    // givenの共通項目の指定
    public static RequestSpecification req() {
        return given()
                .contentType("application/json")
                .accept("application/json");
    }
}

ではこれを使って書き直すと下記のようになります。

package testcase;

import client.ApiClient;
import org.junit.jupiter.api.Test;
import static org.hamcrest.Matchers.*;

public class Step1_GetFieldAssertionTest {

    @Test
    void should_match_nested_fields() {
        String path = "/users/2";

        ApiClient.req()
                .when()
                    .get(path)
                .then()
                    .log().ifValidationFails()
                    .statusCode(200)
                    .body("userId", equalTo(2))
                    .body("firstName", equalTo("yamada"))
                    .body("lastName", equalTo("hanako"));
    }
}

最初のべた書きの場合に比べてインデントは深くなりましたがgiven().~ApiClient.req()に集約されてダイエットされました。
今の時点では1つのエンドポイントを見ているので、あまり意味はないですが、テストケースが増えると聞いてきます。
つまり次の章からはこれを導入していきます。

実行結果はこちらです。

下記は流す前に検証必要

下記の記事ではgiven時にprettyprintを設定しておりその方が見やすいかと思います。
参考:https://qiita.com/shimashima35/items/b85228d5bf5ec2708c5a

POSTメソッドのテスト

愚直に書いてみよう

GETと変わる箇所は以下です。

  • given
    • body リクエストボディーの指定
  • when
    • post

では愚直にrequestBodyに突っ込むjsonをコードにべた書きしてpostしてみましょう。

package testcase;

import client.ApiClient;
import org.junit.jupiter.api.Test;

import static org.hamcrest.Matchers.*;

public class Step2_PostFieldAssertionTest {

    @Test
    void should_register_user() {
        String path = "/users/register";

        String requestBody = """
                    {
                        "firstName": "yamada",
                        "lastName": "hanako",
                        "password": "test1234!"
                    }
                """;

        ApiClient.req()
                    .body(requestBody)
                .when()
                    .post(path)
                .then()
                    .log().ifValidationFails()
                    .statusCode(200)
                    .body("userId", notNullValue())
                    .body("firstName", equalTo("yamada"))
                    .body("lastName", equalTo("hanako"));
    }
}

実行結果
image.png

拡張性を持たせてみよう

上記のテストはstring型でリクエストjsonをべた書きしています。
この形だと、仮にこのリクエストjsonをほかでも使いたいとかの事例が出てきたり、したときに再利用できません。
また、コードの中にリクエストjsonがあるのでコード自体が長くなって読みづらくなります。
では、次にjsonファイルを作ってそれを読み込む形にしてみましょう。
プロジェクトルートディレクトリのsrc/test配下は下記のような構造になります

C:.
├─client
│      ApiClient.java
│
├─resources
│  │  application-test.properties
│  │
│  └─request
│          success_user_register.json
│
└─testcase
        Step1_GetFieldAssertionTest.java
        Step2_PostFieldAssertionTest.java
package testcase;

import client.ApiClient;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import static org.hamcrest.Matchers.*;

public class Step2_PostFieldAssertionTest {

    @Test
    void should_register_user() throws IOException {
        String path = "/users/register";
        InputStream is = getClass().getClassLoader()
                    .getResourceAsStream("request/success_user_register.json");

        String requestBody = new String(is.readAllBytes(), StandardCharsets.UTF_8);

        ApiClient.req()
                    .body(requestBody)
                    .log().all()
                .when()
                    .post(path)
                .then()
                    .log().all()
                    .statusCode(200)
                    .body("userId", notNullValue())
                    .body("firstName", equalTo("yamada"))
                      .body("lastName", equalTo("hanako"));
    }
}

ついでに今回はすべてのログを出すように変えてみたので、そのログも見てみます。
image.png

ログはこのようになっておりPOST時のリクエストボディーとレスポンスの値がちゃんとあることが分かります。

Request method:	POST
Request URI:	http://localhost:8080/users/register
Proxy:			<none>
Request params:	<none>
Query params:	<none>
Form params:	<none>
Path params:	<none>
Headers:		Accept=application/json
				Content-Type=application/json
Cookies:		<none>
Multiparts:		<none>
Body:
{
    "firstName": "yamada",
    "lastName": "hanako",
    "password": "test1234!"
}
HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sat, 11 Apr 2026 01:04:36 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
    "firstName": "yamada",
    "lastName": "hanako",
    "userId": 36
}

番外編(間違った値で比較してみる)

せっかくなので、間違えてみましょう。
assert側のfirstNamekagawalastNamedaichiにしましょう。
image.png

image.png

ログを見てみるとfirstNameは期待値と実際の値を出してくれていますね。
一方でlastNameは後に検証しているので出ていません。

全部出すという方向にする場合はJunit側でassertAllを使って比較をするとできます。
結果はこんな感じです。
image.png

Junitで全部比較する場合のコード
package testcase;

import client.ApiClient;
import io.restassured.response.Response;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class Step2_PostFieldAssertionTest {

    @Test
    void should_register_user() throws IOException {
        String path = "/users/register";
        InputStream is = getClass().getClassLoader()
                .getResourceAsStream("request/success_user_register.json");

        String requestBody = new String(is.readAllBytes(), StandardCharsets.UTF_8);

        Response response = ApiClient.req()
                    .body(requestBody)
                    .log().all()
                .when()
                    .post(path);

        String firstName = response.jsonPath().getString("firstName");
        String lastName = response.jsonPath().getString("lastName");
        assertAll(
                () -> assertEquals(200, response.statusCode()),
                () -> assertEquals("kagawa", firstName),
                () -> assertEquals("daichi", lastName)
        );
    }
}

最後に

このテストのPoC記事は後2つ書くつもりで、残りは応用編とシナリオテスト編です。
今回でRPGで言うと最低限の操作コマンドを覚えた感じなので、応用編はレベルアップして技を覚えていく感じになります。
シナリオテスト編はその技を駆使してよりうまく戦っていこうというような算段です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?