FakeでJetpack ComposeのPreviewとTest二度おいしい

3 min read読了の目安(約3400字

Jetpack ComposeのプラクティスでGoogleがFakeをプッシュしてる理由をダイヤモンド理解した

Jetpack Composeは渡されたパラメータに応じてUIを構築する関数を定義するという宣言的(Declaratice) UIシステムなので、Preview時にもパラメータが必要です。
小さなパーツならともかく、画面全体をプレビューするとなると準備するデータも大きく煩雑でしょう。
あれ、こういうデータオブジェクトの用意、いつもUnit testやUI testのためにしてたような。

そこでJetpack ComposeのプラクティスではデータをMockする代わりにFakeを用意します。

Composable-sampleのJetNewsではdataディレクトリの下にimplがあり、PostsRepositoryを実装した2つのFakeリポジトリが用意されています。
FakePostsRepositoryはJetNewsがサンプルであるためネットワークアクセスを行わずデータを取得しますが、実際のアプリで使われるRepositoryの実装です。本来であれば本当にネットワークアクセスを行う実装になっているはずです。
BlockingFakePostsRepositoryはテスト用のFakeデータを返却するRepositoryですが、奇しくもFakePostsRepositoryがサンプル用に同じようにFakeデータを返しているので非常によく似た実装になっておりややこしいです。
このFakeRepositoryたちが返却しているデータはベタ書きのデータオブジェクトとしてPostsDataに定義されています。

この2つの実装をJetNewsではアプリで使うFakePostsRepositoryを持ったAppContainerと、テストで使うBlockingFakePostsRepositoryを持ったTestAppContainerを切り替えています。残念ながらJetNewsではTestAppContainerで提供されたFakeRepositoryを使ったテストはありませんでしたが、Composeの単体テストなどでこのFakeを役立てることができるかもしれません。

そしてこのFakeをPreviewでも使っています。HomeScreenではPreview用のCompose functionを定義し、BlockingFakePostsRepositoryを通してプレビュー用のFakeデータをPreviewに渡しています。

@Preview("Home screen")
@Composable
fun PreviewHomeScreen() {
    val posts = runBlocking {
        (BlockingFakePostsRepository().getPosts() as Result.Success).data
    }
    JetnewsTheme {
        HomeScreen(
            posts = UiState(data = posts),
            favorites = setOf(),
            ...

ComposeをつかったアーキテクチャでFakeを使う2つの利点が確認できました。

  • テスト用のFake実装をテスト間で使い回せる
  • 開発時のプレビューにFakeを使える

ただしComposeはまだ途上でありこのプラクティスが常にベストとは限りません。

JetNewsの例ではFakeは本番でも使われるため同一のModuleに含まれていましたが、これはビルドしたアプリにFakeのデータが含まれるということです。
本番で本当にネットワークアクセスを実装した場合でも、不用意にComposableのPreviewにFakeデータを使って参照が残るとコードシュリンクの対象にならず、ビルドしたアプリにもFakeデータが含まれてしまう可能性があります。
JetNewsではPreviewはHomeScreen Composableそのものではなく、HomeScreenをCompose functionでくるんだPreviewHomeScreenのPreviewとして表示しています。
未確認ですが、PreviewHomeScreen関数はアプリでは呼び出されないためコードシュリンクの対象になると考えられます。
コードシュリンクに依存するのが不安であればプレビュー用のModuleでも作ればよいかもしれませんがやりすぎかもしれません。

また、FakeがあるからといってMockが不要になるわけではありません。MockitoやMockkなどのモックライブラリはモックした関数の呼び出しカウントや異なる返却値のモックなどを柔軟に行うことができ、ロジックのテストには引き続き欠かせません。
ComposeのPreviewやテストでFakeを使う場合にも柔軟に返却値を変更できるようにしたい場合はあらかじめ返却値を変更できるような関数を作っておく必要があります。