この記事は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()
これで無事テストが成功しました。