LoginSignup
3
1

More than 5 years have passed since last update.

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

Posted at

概要

サーバーサイドKotlinフレームワークのKtorのスモークテストやるぞ!というわけで今回は、以下のドキュメントからgetとpostのテスティングを抽出して記事化しました!
Testing Server Applications

環境

  • 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

構成

今回はDocker Composeで構築したnginxにKtorを導入してます(割愛)

コード

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

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の場合の期待値はルートディレクトリにリダイレクトされるのを正としています。

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数が増えるため上限に気をつけること

参考

3
1
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
3
1