ContentProviderのテストで予期しているExceptionが返らずnullが返ってくる原因
背景
下記のようなContentProvider.queryの形はよく見る。
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?,
): Cursor? {
return when (uriMatcher.match(uri)) {
ID_1 -> getSomething1()
ID_2 -> getSomething2()
else -> throw IllegalArgumentException("Unknown URI: $uri")
}
}
上記の処理としては、「ID_1
またはID_2
に該当するURIが渡されれば適切なCursor
を発行して、そうでなければ例外を投げる」というもの。
しかし、「例外をしっかり投げてくれるかのテスト」を作成したときに上手くいかなかったのでメモを残すことにした。
そもそも例外投げるだけのケースをテストする必要があるかどうかは分からないが…。
@Test(expected = IllegalArgumentException::class)
fun test_query_invalidUri() {
resolver.query(Uri.parse(INVALID_URI), null, null, null, null)
}
companion object {
private val INVALID_URI = "invalid_uri"
}
ちなみに上記のテストは例外は投げられず代わりにnull
がquery
から返却される。
上記コードはKotlinだがJavaでも同様。
解消方法
結論から言うと、INVALID_URI
がURIのフォーマットになっていないからである。
@Test(expected = IllegalArgumentException::class)
fun test_query_invalidUri() {
resolver.query(Uri.parse(INVALID_URI), null, null, null, null)
}
companion object {
private val INVALID_URI = "content://tech.araki.provider/invalid_uri"
}
のような形であれば問題なく独自のContentProvider
で定義したquery
が呼ばれて、例外を返してくれる。
どうやら、URIのフォーマットが適切でない場合、ContentResolver.query
側で早期にnull
を返す仕組みになっているらしい。
補足
公式ページにもそれらしき文言が書いてあった。
これはもしかしたら、insert
などの他のメソッドでもURIフォーマットが不適切だったらProviderまで届かないのかもしれない。(未調査)
ContentProvider.query() メソッドは Cursor オブジェクトを返す必要があります。失敗した場合は、Exception をスローします。SQLite データベースをデータ ストレージとして使用する場合は、SQLiteDatabase クラスのいずれかの query() メソッドが返す Cursor を返します。クエリがどの行にも一致しない場合は、getCount() メソッドが 0 を返す Cursor インスタンスを返す必要があります。クエリプロセス中に内部エラーが発生した場合にのみ null を返します。
SQLite データベースをデータ ストレージとして使用しない場合は、Cursor クラスのいずれかの具象サブクラスを使用します。たとえば、MatrixCursor クラスは、各行が Object の配列となるカーソルを実装します。このクラスでは、addRow() を使って新しい行を追加します。
Android システムは、プロセスの境界をまたいで Exception を送信できるように設定する必要があることにご注意ください。Android では、クエリエラーを処理するのに便利な、次の例外を送信できます。