Kotlin Springboot -- Part 21 任意の key value の json を POST する API E2E を書く
Tags: kotlin, ca, kluent, jackson
Published: 2022-12-11 15:25:11

## why

前回の記事で /persons の GET の API E2E ができた
次に POST の API E2E を作りたい。

---

## シナリオを定義する

```php
## Persons を登録する
* "Siro"と"40"のデータで新規登録する
* "201"が返ってくる
```

201 返ってくるところまでをテストする
UUID は見ない。UUID が生成された後に 201 が現れると想定する。

---

## HTTP リクエストに データを詰めるため、依存を追加する

https://zetcode.com/kotlin/getpostrequest/

今回も get のテストを作った時と同じく
zetcode のやり方を参考にする

kotlin の map 型で定義した値を HttpRequest の body に詰められる形にするには objectMapper が必要。

objectMapper を使うためには jackson-module-kotlin のライブラリが必要。

しかし、JsonPathKt が暗黙的に jackson-module-kotlin に依存している。

よって、JsonPathKt が既に入っているこのプロジェクトでは jackson-module-kotlin は必要ない。

```kotlin
import com.fasterxml.jackson.databind.ObjectMapper

    @Step("<name>と<age>のデータで新規登録する")
    fun postPersonsWithData(name: String, age: Int) {
        val objectMapper = ObjectMapper();

    }
```

このように、POM に jackson がなくても ObjectMapper を使うことができた。

---

## リクエストのボディに突っ込める json 型に map 型から変換する

```kotlin
        val objectMapper = ObjectMapper();
        val values = mapOf("name" to name, "age" to age)
        val requestBody: String = objectMapper.writeValueAsString(values)
```

import した ObjectMapper のインスタンスを作成して
引数から渡ってきた name と age を key value の kotlin map にする。

その kotlin map を objectMapper の writeValueAsString() に渡す。

こうすることで 

```kotlin
 <{name=Siro, age=40}>
```

map 型の key=value の、この形から

```kotlin
 <{"name":"Siro","age":40}>
```

json 型の key:value の、この形に変換することができる。

---

## json 型にした requestBody を使って POST リクエストを作成する

```kotlin
val request = HttpRequest.newBuilder()
    .uri(URI.create("http://127.0.0.1:8080/persons/"))
    .POST(HttpRequest.BodyPublishers.ofString(requestBody))
    .header("Content-Type", "application/json")
    .build();
```

GET の時と同じように、
HttpRequest の newBuilder を使って基礎を作って
.uri(URI.create("your.url.com")) でURI からリクエストを作成する

POST の場合は .POST() で
HttpRequest の BodyPublishers を使って
先程作成したボディに値を詰めてリクエストを作成する。

さらに、.header で Content-Type に application/json を指定しないと、REST API では 415 Error になってしまう。
( これは参考にした記事には載っていなかった )

---

## 作成した body 込みのリクエストを送信してレスポンスを受け取れるようにする

これでリクエストは作れたので、送信して結果のレスポンスを受け取って
表示してみる。

```kotlin
val client = HttpClient.newBuilder().build();
val responseBodyHandler = HttpResponse.BodyHandlers.ofString()
val response = client.send(request, responseBodyHandler)

response shouldBeEqualTo "test"
```

クライアントのインスタンスを作って
レスポンスのボディハンドラのインスタンスも作って

クライアントでリクエストとレスポンスのボディハンドラを使って送信する。

```kotlin
 Expected: <test> but was: 
<(POST http://127.0.0.1:8080/persons/) 405>
```

中身をみてみると、ちゃんとレスポンスが入っている事がわかった。

まだ受け付けていないので、405 Method Not Allowed がでた。

---

## レスポンスをシナリオデータストアに保存する

```kotlin
        ScenarioDataStore.put("StatusCode", response.statusCode())
```

レスポンスのステータスコードをシナリオデータストアに保存する。
GET のときと同じ。

```kotlin
    @Step("<statusCode>が返ってくる")
    fun shouldBeStatusCode(statusCode: Int) {
        ScenarioDataStore.get("StatusCode") shouldBeEqualTo statusCode
    }
```

これで、次のステップでステータスコードをアサートする事ができる。

---

## 実際に通しで実行してみる

```php
## Persons を登録する
* "Siro"と"40"のデータで新規登録する
* "201"が返ってくる
```

これでシナリオを実行すると

```php
# Person API のテスト
  ## Persons を取得する  ✔ ✔ ✔ ✔ ✔
  ## Persons を登録する  ✔ ✘
        Failed Step: "201"が返ってくる
        Specification: specs/person.spec:12
        Error Message: org.amshove.kluent.internal.ComparisonFailedException: 
Expected: <201> but was: <405>
```

しっかり POST でデータを詰めて実行されて
405 が返ってきてしまったことをテスト失敗として確認できた。
まだ API 実装してないからね。

---

## まとめ

POST でデータを詰めて送って
結果をテストするためには

Spec ファイルのシナリオでデータを渡して
Step の実装で
データを map 型にして
jackson ライブラリの ObjectMapper で json 型に変換して
HttpRequest.BodyPublishers で Body に詰めた POST リクエストを作って

リクエストを送って、レスポンスをシナリオデータストアに保存して
次のステップでシナリオデータストアからレスポンスをアサートする。

以上。

---

## 伸びしろ

指定の json ファイルのデータを POST した結果のテストもしたい