DaggerMock:让 Dagger 2 的测试不再难写

DaggerMock 在 GitHub 上拿到 1.1K Star,是 fabioCollini 维护的一个 JUnit 规则,专门用来解决 Dagger 2 依赖注入框架的单元测试难题。

Android 开发里用 Dagger 2 做依赖注入的项目不少,但写测试的时候麻烦就来了。要 mock 一个被 Dagger 管理的对象,得写一个 TestModule,再写一个 TestComponent,把原来的模块替掉,才能让 mock 生效。一个测试类要写两到三个辅助类,代码量比业务逻辑还多。

正文顶部截图

1、 解决什么问题

DaggerMock 解决的就是这层样板代码。它是一个 JUnit Rule,挂上之后会扫描测试类里所有被 @Mock 注解的字段,自动生成一个替身模块,把原来的 provide 方法覆盖掉。测试代码里只需要在字段上打 @Mock,剩下的事情规则自己处理。

写出来的测试长这样:

public class MainServiceTest {
    @Rule public DaggerMockRule<MyComponent> rule =
        new DaggerMockRule<>(MyComponent.class, new MyModule());

    @Mock RestService restService;
    @Mock MyPrinter myPrinter;
    @InjectFromComponent MainService mainService;

    @Test
    public void testDoSomething() {
        when(restService.getSomething()).thenReturn("abc");
        mainService.doSomething();
        verify(myPrinter).print("ABC");
    }
}

Kotlin 版本更短,靠 DaggerMock.rule<T>(module) 配合 mock() 函数,几行就够了。

2、 怎么做到的

背后原理不算复杂。DaggerMock 在运行时动态生成一个继承自原 Module 的子类,把里面每个 provide 方法都重写成返回 mock 对象。然后把这个动态 Module 喂给原 Component 构建出新的实例,再交还给测试类使用。

一个限制是:被覆盖的对象必须在 Module 里有对应的 provide 方法。用 @Inject 注解直接构造的对象,DaggerMock 无能为力。从 0.6 版本开始,如果测试类里出现了无法被覆盖的字段类型,会直接抛运行时错误,避免测试假阳性的情况。

README区域截图

3、 不只是单元测试

DaggerMock 同时支持 Espresso 和 Robolectric 两种测试场景。Espresso 用例里挂上规则之后,可以在 Activity 启动之前就给 mock 设好行为,启动之后再做 verify。Robolectric 的用法类似,区别在于 Component 是挂到 RuntimeEnvironment 上而不是 Application。

子组件(subcomponent)从 0.6 开始支持,但有个前提:父组件里创建子组件的方法必须显式接收子组件的 Module 作为参数。如果是用 @Binds 注解的抽象方法或者静态方法定义的 Module,DaggerMock 处理不了,这部分对象的替换会失效。

Dagger Android 也能用,限制更多一些:只支持 Espresso 测试,subcomponent 和 dependent component 里的对象没法替换,并且必须通过 customizeBuilder 方法手动把 Application 实例塞给 Component 的 Builder。

4、 Kotlin 项目的注意点

Kotlin 的类默认是 final 的,Mockito 没法直接 mock。三种解法:

  • 把相关类显式标成 open,缺点是生产代码也跟着开了
  • 引入 mockito-inline 或 mock-maker,配合 dexopener,示例在 RealWorldAppKotlin 模块里
  • 用 kotlin-allopen 编译器插件,只在编译期给类加 open 修饰,示例在 RealWorldAppKotlinAllOpen 模块里

第三种方式对生产代码无侵入,是 Kotlin 项目里推荐的做法。

5、 接入方式

DaggerMock 通过 JitPack 分发,先在根 build.gradle 加上 JitPack 仓库:

maven { url "https://jitpack.io" }

模块里加依赖:

testImplementation 'com.github.fabioCollini.daggermock:daggermock:0.8.5'
androidTestImplementation 'com.github.fabioCollini.daggermock:daggermock:0.8.5'

Kotlin 项目额外加 daggermock-kotlin 这个 artifact,能省掉一些泛型样板代码。

6、 谁适合用

  • 写 Android 应用、用 Dagger 2 做依赖注入、想要补单元测试的开发者
  • 项目里 Module 数量多、每次写测试都要重复造 TestModule 轮子的人
  • Espresso UI 测试里需要 mock 网络层或数据层的场景
  • 团队里 Kotlin 类默认 final 导致 Mockito 失效的情况

项目本身的代码量不大,核心逻辑集中在 DaggerMockRule 这一个类里。读源码能看清楚 JUnit Rule 的工作机制,以及如何在运行时通过反射动态替 Dagger 的 Module。对理解 Dagger 的 Component/Module/Component Builder 之间的关系也有帮助。

运行时通过反射动态替 Dagger 的 Module。对理解 Dagger 的 Component/Module/Component Builder 之间的关系也有帮助。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐