Android动态功能模块插桩测试如何伪造Dagger依赖注入
动态特征模块Dagger依赖测试替换方案
核心问题是EditNoteActivity内硬编码了正式版EditNoteComponent的初始化逻辑,测试环境下无法介入替换依赖。可按以下三种方案改造,适配动态特征模块的测试注入需求:
方案1:抽象组件工厂,通过Application层暴露组件创建入口
该方案对现有代码侵入性最低,最适配动态模块架构:
- 先在
app主模块定义跨模块的组件工厂接口,避免动态模块直接硬编码Dagger生成类:
// app模块内定义 interface EditNoteComponentProvider { fun provideEditNoteComponent(module: EditNoteModule = EditNoteModule()): EditNoteComponent }
- 主模块的
NoteTakingApplication实现该接口,正式环境下返回正式组件:
open class NoteTakingApplication : Application(), EditNoteComponentProvider { lateinit var appComponent: ApplicationComponent private set override fun onCreate() { super.onCreate() appComponent = initializeAppComponent() } open fun initializeAppComponent(): ApplicationComponent { return DaggerApplicationComponent.create() } override fun provideEditNoteComponent(module: EditNoteModule): EditNoteComponent { return DaggerEditNoteComponent.builder() .applicationModule(appComponent.applicationModule) .editNoteModule(module) .build() } }
注意:这里不要用DaggerEditNoteComponent.create(),改用builder传入全局的ApplicationModule实例,避免动态模块重复创建app层组件,也更符合Dagger在动态模块的最佳实践
- 改造
EditNoteActivity的注入逻辑,不再硬编码Dagger创建逻辑,而是从Application获取组件:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 从Application获取组件,不再直接引用硬编码的Dagger生成类 (application as EditNoteComponentProvider) .provideEditNoteComponent() .inject(this) setContent { NoteTakingAppTheme { EditNoteScreen( tutorialText = tutorialText, onBackClick = { finish() } ) } } }
- 测试环境下,覆写测试用Application的组件提供逻辑,传入测试模块即可:
class MyTestApplication : NoteTakingApplication() { override fun initializeAppComponent(): ApplicationComponent { return DaggerTestApplicationComponent.create() } override fun provideEditNoteComponent(module: EditNoteModule): EditNoteComponent { // 替换为测试专用的EditNoteModule return DaggerEditNoteComponent.builder() .applicationModule(TestApplicationModule()) .editNoteModule(TestEditNoteModule()) .build() } }
配合已实现的MyCustomTestRunner,测试时所有EditNoteActivity的注入都会自动使用测试模块提供的依赖。
方案2:通过Hilt替换原生Dagger,自动适配动态模块测试
如果项目允许迁移依赖注入框架,直接用Hilt是长期维护成本最低的方案:
- Hilt原生支持动态特征模块的依赖注入,不需要手动管理跨模块的组件生命周期
- 测试时直接用
@BindValue、@TestInstallIn或者自定义TestRule就能替换对应模块的依赖,不需要自定义TestRunner、不需要覆写Application逻辑 - 不需要在Activity里硬编码组件创建代码,Hilt会自动完成注入
方案3:测试时通过字节码插桩替换硬编码逻辑(零业务代码侵入)
如果不想改现有业务代码,可以在UI测试的build variant里加入专门的构建转换逻辑:
- 在androidTest的构建配置里,把
DaggerEditNoteComponent类替换为测试版本的实现,或者用ASM字节码插桩,修改EditNoteActivity里DaggerEditNoteComponent.create()的调用逻辑,返回传入了TestEditNoteModule的组件实例 - 该方案不需要改业务代码,但构建配置复杂度高,后续Dagger版本升级、代码混淆时容易出兼容问题,仅适合无法重构业务代码的场景使用。
动态特征模块不要直接用
DaggerXxxComponent.create()创建依赖app层的组件,这种写法会导致app层的单例依赖重复创建,也会彻底堵死测试时替换依赖的入口,始终通过全局Application实例持有顶层组件、向下提供子组件是更稳妥的实现方式。
内容的提问来源于stack exchange,提问作者user1105872
相关产品推荐
相关产品推荐

