この記事はkdnaktの1人 Advent Calendar 2020の13日目の記事です。
2020年は会社でKotlin dojoを主催して週1回30分Kotlinと戯れていました。12月はその集大成ということで、Kotlin/NativeでHTTPサーバーを作ってみたいと思います。どこまでできるか、お楽しみ……。
[assertFailsWithで例外をテスト]
HTTPメソッドはGET、POST、DELETEなど8つと決まっています。Getのように小文字が含まれている場合も無効なHTTPメソッドとなります。無効なHTTPメソッド文字列が送られてきた場合には、400 Bad RequestのHTTPレスポンスを返さなくてはなりません。
RFC 7230 - 3.1.1. Request Line
これは、HTTPサーバーがHTTPレスポンスを受け取ってstart-lineがrequest-lineの代わりにstatus-lineであった場合も同様です。
ReequestParser.ktに、この機能を追加します。
まず、ReequestParser.ktファイルに、以下のようにBadRequestExceptionを定義します。
abstract class HttpException(val status: Int, val reason: String): RuntimeException() class BadRequestException(): HttpException(400, "Bad Request")
つぎに、テストケースを追加します。
JavaでJUnitを利用している場合は以下のように、@Testアノテーションに、expectedフィールドを使って発生させたい例外クラスを指定することで、例外をテストすることができました。
@Test(expected = IllegalArgumentException.class)
public void testFailsWithIllegalArgumentException() {
// do some test
}
Kotlinでも同様に実装できるかと思い試してみましたが、コンパイルエラーになってしまいました。

Googleで「kotlin test exception expect」と検索してみると、公式サイトが見つかりました。assertFailsWithという関数を使えば例外のテストを実装できそうです。
assertFailsWith - Kotlin Programming Language
ただ、公式サイトをみても実装方法が理解できなかったので、以下のブログを参考にしました。
assertFailsWith<IllegalArgumentException> { someMethod(invalidArg) }
これを参考に、BadRequestExceptionが投げられるかどうか確認するテストを以下のように実装します。
@Test
fun shouldNotParseHttpResponse() {
val resByteArray = ("HTTP/1.1 200 OK" + crlf
+ crlf).encodeToByteArray()
val actual = assertFailsWith<BadRequestException> { parser.parse(resByteArray) }
assertEquals(400, actual.status)
assertEquals("Bad Request", actual.reason)
}
テストを実行すると、BadRequestExceptionを投げる部分は実装されていないので、以下のようにテストが失敗します。
kotlin.AssertionError: Expected an exception of com.kdnakt.kttpd.BadRequestException to be thrown, but was kotlin.Exception: Invalid enum value name: HTTP/1.1
enumとして定義したHttpMethodのnameに合致しない文字列が引数として渡されたため、HttpMethod.valueOf()の部分がIllegalArgumentExceptionを投げています。
これは、単純に以下のように修正することもできます。
try {
return RequestContext(
HttpMethod.valueOf(startLineElements[0]),
startLineElements[1],
httpMethod
)
} catch (e: IllegalArgumentException) {
e.printStackTrace()
throw BadRequestException()
}
しかし、try-catchブロックで囲むコード量が多く、他の例外もキャッチしてしまいそうです。文字列からenumを生成するのに良い方法はないか探したところ、以下のStack OverflowでfirstOrNullを利用したコードを見つけました。
これを参考に、以下のように実装を修正します。nullだった場合には、エルビス演算子?:を利用してBadRequestExceptionを投げます。
val httpMethod = HttpMethod.values()
.firstOrNull { it.name == startLineElements[0] }
?: throw BadRequestException()
これで無事テストが成功しました。
