Ktorでテスト入門 〜とりあえずgetとpostをやってみる〜

## 概要
サーバーサイドKotlinフレームワークのKtorのスモークテストやるぞ！というわけで今回は、以下のドキュメントからgetとpostのテスティングを抽出して記事化しました！
[Testing Server Applications](https://ktor.io/servers/testing.html)

## 環境
- macOS High Sierra 10.13.6
- IntelliJ IDEA 2018.3.1
- Java 1.8.0_191
- Kotlin 1.3.11
- Ktor 1.1.1

Ktorの環境構築とかはこちらが参考になるかと。
[KtorでHello World](https://qiita.com/l4stflowers/items/b93b4d0ebd050e9b9c7e)

## 構成
今回はDocker Composeで構築したnginxにKtorを導入してます（割愛）

## コード
まず、共通処理としてテスト用のControllerを作成します。
doWithTestApplicationメソッドは設定読み込みとバグ回収のためwith(engine)を読み出しています。
この設定を読み込む際にDB周りも初期化されるため、DB Connection数の上限に気をつけてください。
getメソッドとpostメソッドはそれぞれのエンドポイントに対して、それぞれのメソッドで呼び出すためのものです。doWithTestApplicationでラップして、そのコールバックに処理を書いています。
基本的にはRequest Headerの設定とpostの場合のみ、bodyの指定を行っています。

```kotlin:APITestController.kt
@KtorExperimentalAPI
class APITestController {
    private fun doWithTestApplication(proc: TestApplicationEngine.() -> Unit) {
        val engine = TestApplicationEngine(createTestEnvironment {
            config = ///設定を読み込む処理
        })
        engine.start(wait = false)

        /// https://github.com/ktorio/ktor/issues/358#issuecomment-372897523
        with(engine) {
            proc()
        }
    }
    fun get(url: String, headers: Headers? = null, proc: TestApplicationCall.() -> Unit) = doWithTestApplication {
        handleRequest(HttpMethod.Get, url) {
            headers?.forEach { s, list ->
                list.forEach {
                    addHeader(s, it)
                }
            }
        }.proc()
    }

    fun post(url: String, body: String, headers: Headers? = null,  proc: TestApplicationCall.() -> Unit ) = doWithTestApplication {
        handleRequest(HttpMethod.Post, url) {
            headers?.forEach { s, list ->
                list.forEach {
                    addHeader(s, it)
                }
            }
            setBody(body)
        }.proc()
    }
}
```

テストケース側では、上記のControllerを使ってテストケースを組み立てます。
getの場合、header指定がないのであればctrl.get("エンドポイント")みたいな書式でかけます。
コールバックに正常系期待値判定を行います。以下の/xxx/aaa の場合、httpレスポンスが200なら正、としています。
postの場合、ボディとヘッダーの指定が必要となります。（最初ヘッダーを指定していなくてエラーでハマった…）
ボディはlistOf("key" to "value").fromUrlEncode()とすることで比較的平易に値の設定が可能になっています。
ヘッダーはHeaders.buildのコールバックにappendで HttpHeadersのプロパティ値をキーに、値は例にあるように、たとえばContentTypeであればContentType.Application.FormUrlEncoded.toString()のような形で指定可能です。最初、content typeを未指定にしていたところpostがエラーになってしまい、なかなか原因追求ができませんでした（分かれば当たり前なのですけれど）ちなみに、postの場合の期待値はルートディレクトリにリダイレクトされるのを正としています。

```kotlin:XXXControllerTest.kt
@KtorExperimentalAPI
class XXXControllerTest {

    val ctrl = APITestController()

    @Test
    fun recreate() = ctrl.get("/xxx/aaa") {
        assertEquals(HttpStatusCode.OK, response.status())
    }

    @Test
    fun recreatePost() = ctrl.post("/xxx/aaa",
                                    listOf("email" to "example@example.com").formUrlEncode(),
                                    Headers.build { append(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded.toString()) }) {
        assertEquals(HttpStatusCode.Found, response.status())
        assertEquals("/", response.headers.get("Location"))
    }
}
```

## 結果
こんな感じでルーティングの各エンドポイントを網羅することでスモークテストが実現可能になりました！

## ハマったこと・課題など
- postのときのヘッダー設定がなかなかわからなかった
- TestApplicationEngine を作成するたびに DB connection数が増えるため上限に気をつけること

## 参考
- [Testing Server Applications](https://ktor.io/servers/testing.html)