概要
サーバーサイド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の指定を行っています。
@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の場合の期待値はルートディレクトリにリダイレクトされるのを正としています。
@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数が増えるため上限に気をつけること