Spekラムダ内でKoinモジュールをinjectする
TL;DL
KoinTestを継承したRootクラスと、それをレシーバとするラムダを引数に持つ抽象クラスを実装する。
class KoinRoot(val root: Root) : KoinTest, Root by root abstract class KoinSpek(koinSpec: KoinRoot.() -> Unit) : Spek({ koinSpec.invoke(KoinRoot(this)) }) object TestSpec: KoinSpek({ beforeEachTest { startKoin { modules(listOf(testAppModule)) } } val viewModel: TestViewModel by inject() })
導入
- Spekについて
QA体制が十分でないときや強力なプロダクトオーナーが不在のときにバグを減らす手段としてビヘイビア駆動開発(BDD)は結構有効だと思っていて、そういう現場でテストフレームワークとしてSpekを選択するのはアリだと思っている。
- Koinについて
Dagger2よりシンプルでラーニングコストが低いので熟練のAndroidエンジニアが少なくアプリケーションの要件も複雑でない場合にKoinをDIコンテナとして選択するのはアリだと思っている。
両方とも開発体制が整っていない現場で選択肢となりうる技術かなと思っているが組み合わせて使おうと思うと途端にドキュメントが減って、逆に辛いことになること学んだわけだが、しばらくはこの組み合わせでやっていこうと思っているので備忘録的に残しておく。
概要
KoinTest
/** * Get an instance from Koin */ inline fun <reified T> KoinTest.get( qualifier: Qualifier? = null, noinline parameters: ParametersDefinition? = null ): T = getKoin().get(qualifier, parameters) /** * Lazy inject an instance from Koin */ inline fun <reified T> KoinTest.inject( qualifier: Qualifier? = null, noinline parameters: ParametersDefinition? = null ): Lazy<T> = lazy { get<T>(qualifier, parameters) }
Spek
abstract class Spek(val root: Root.() -> Unit) @SpekDsl interface Root : GroupBody { fun registerListener(listener: LifecycleListener) fun include(spek: Spek) = spek.root(this) }
実装
KoinTestを継承したRootクラスと、それをレシーバとするラムダを引数に持つ抽象クラスを実装する。
Spekはレシーバ付きラムダRootをコンストラクタに持つabstract class. RootがKoinTestを継承するとSpekオブジェクト内でKoinコンポネントのinjectが可能になるので、 KoinTestを継承しRootに処理を移譲するKoinRootを定義する。
class KoinRoot(val root: Root) : KoinTest, Root by root
上記KoinRootをレシーバとするラムダを引数に持ちレシーバ付きラムダを実行するabstract classを定義する。
abstract class KoinSpek(koinSpec: KoinRoot.() -> Unit) : Spek({ koinSpec.invoke(KoinRoot(this)) })