安卓单元测试 (十):DaggerMock, 让 Dagger2 与单元测试的结合易如反掌

The Old Way

咱们在系列的第六篇文章前面介绍了Dagger2在单元测试里面的使用姿式。大体过程是这样的,首先,你要mock出一个Module,让它的某个Provider方法在被调用的时候,返回你想到的mock的Dependency。而后使用这个mock的module来build出一个Component,再把这个Component放到你的ComponentHolder。举个例子说明一下,假设你有一个LoginActivity,里面有一个LoginPresenter,是经过Dagger2 inject进去的,以下:java

public class LoginActivity extends AppCompatActivity {
    @Inject
    LoginPresenter mLoginPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //...other code

        ComponentHolder.getAppComponent().inject(this);
    }
}

//对应的Test类以下:

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class LoginActivityTest {
    @Test
    public void testLogin() {
        AppModule mockAppModule = Mockito.mock(AppModule.class);
        LoginPresenter mockLoginPresenter = mock(LoginPresenter.class);
        Mockito.when(mockAppModule.provideLoginPresenter(any(UserManager.class), any(PasswordValidator.class))).thenReturn(mockLoginPresenter);  //当mockAppModule的provideLoginPresenter()方法被调用时,让它返回mockLoginPresenter
        AppComponent appComponent = DaggerAppComponent.builder().appModule(mockAppModule).build();  //用mockAppModule来建立DaggerAppComponent
        ComponentHolder.setAppComponent(appComponent);  //假设你的Component是放在ComponentHolder里面的

        LoginActivity loginActivity = Robolectric.setupActivity(LoginActivity.class);
        ((EditText) loginActivity.findViewById(R.id.username)).setText("xiaochuang");
        ((EditText) loginActivity.findViewById(R.id.password)).setText("xiaochuang is handsome");
        loginActivity.findViewById(R.id.login).performClick();    

        verify(mockLoginPresenter).login("xiaochuang", "xiaochuang is handsome");
    }
}复制代码

能够看到,为了让Dagger2返回一个Mock对象,咱们须要写5行代码。再多写几个测试,我保证你必定会以为繁琐的。固然,咱们可使用前一篇文章里面说的方式,和其它的一些手段,来简化代码,如下是我做出的一些努力,应该说,代码已经比较简洁了:android

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class LoginActivityTest {

    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Mock
    LoginPresenter loginPresenter;

    @Test
    public void testLogin() {
        Mockito.when(TestUtils.appModule.provideLoginPresenter(any(UserManager.class), any(PasswordValidator.class))).thenReturn(loginPresenter);  //当mockAppModule的provideLoginPresenter()方法被调用时,让它返回mockLoginPresenter
        TestUtils.setupDagger();

        LoginActivity loginActivity = Robolectric.setupActivity(LoginActivity.class);
        ((EditText) loginActivity.findViewById(R.id.username)).setText("xiaochuang");
        ((EditText) loginActivity.findViewById(R.id.password)).setText("xiaochuang is handsome");
        loginActivity.findViewById(R.id.login).performClick();

        verify(loginPresenter).login("xiaochuang", "xiaochuang is handsome");
    }

}

public class TestUtils {
    public static final AppModule appModule = spy(new AppModule(RuntimeEnvironment.application));
    public static void setupDagger() {
        AppComponent appComponent = DaggerAppComponent.builder().appModule(appModule).build();
        ComponentHolder.setAppComponent(appComponent);
    }
}复制代码

上面把dagger设置相关的代码减小到了两行,应该说,已经再也不是一个负担了。然而哪怕是这样,若是写多了的话,依然会让人感受略烦,由于这也彻底是Boilerplate code(这里为何要用“也”?)。再多写一点,你就会天然而然的想,若是能有一个工具,能达到这样的效果就行了:咱们在Test类里面定义一个@Mock field(好比上面的loginPresenter),这个工具就能自动把这个field做为dagger的module对应的provider方法(provideLoginPresenter(...))的返回值。也就是说,自动的mock module,让它返回这个@Mock field,而后用这个mock的module来build一个component,并放到ComponentHolder里面去。git

New Hope

Well,我写这篇文章,就是想告诉你们,还真有人写了这样的一个工具,这就是这篇文章要介绍的DaggerMock。它就能达到咱们上面描述的那种效果,让咱们像使用Mockito Annotation同样来定义Mock,却能自动把它们做为Dagger2生产的Dependency。达到的效果以下:github

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class LoginActivityTest {

    @Rule public DaggerRule daggerRule = new DaggerRule();

    @Mock
    LoginPresenter loginPresenter;

    @Test
    public void testLogin_shinny_way() {
        LoginActivity loginActivity = Robolectric.setupActivity(LoginActivity.class);
        ((EditText) loginActivity.findViewById(R.id.username)).setText("xiaochuang");
        ((EditText) loginActivity.findViewById(R.id.password)).setText("xiaochuang is handsome");
        loginActivity.findViewById(R.id.login).performClick();

        verify(loginPresenter).login("xiaochuang", "xiaochuang is handsome");
    }
}复制代码

在上面的代码中,已经没有多余的Boilerplate code,要写的代码,基本是必需写的了。上面起做用的是@Rule public DaggerRule daggerRule = new DaggerRule(); 这行代码。可见,它是经过JUnit Rule来实现的。若是你熟悉JUnit Rule的工做原理,那么你很容易猜到这个DaggerRule的工做原理:app

  1. 初始化一个测试类里面的全部用@Mock field为mock对象(loginPresenter)
  2. mock AppModule,经过反射的方式获得AppModule的全部provider方法,若是有某个方法的返回值是一个LoginPresenter,那么就使用Mockito,让这个方法(provideLoginPresenter(...))被调用时,返回咱们在测试类里面定义的mock loginPresenter
  3. 使用这个mock AppModule来构建一个Component,而且放到ComponentHolder里面去。

我相信看到这里,你必定有不少疑问:maven

  1. 它怎么知道要使用AppModule
  2. 它怎么知道要build什么样的Componant
  3. 它怎么知道要把build出来的Component放到哪?

好吧,其实上面的DaggerRule,不是DaggerMock这个library自带的,是咱们本身实现的。然而别着急,DaggerMock给了咱们提供了一个父类Rule:DaggerMockRule,这个Rule已经帮咱们作了绝大多数事情了。咱们自定义的DaggerRule,其实也是继承自DaggerMockRule的,而咱们在自定义Rule里面作的事情,也只不过是告诉DaggerMock,上面说到的三个问题的答案:要使用哪一个Module、要build哪一个Component、要把build好的Component放到哪,仅此而已。不信请看代码:ide

public class DaggerRule extends DaggerMockRule<AppComponent> {
    public DaggerRule() {
        //告诉DaggerMock要build什么样的Component,使用哪一个module
        super(AppComponent.class, new AppModule(RuntimeEnvironment.application));

        //告诉DaggerMock把build好的Component放到哪
        set(new ComponentSetter<AppComponent>() {
            @Override
            public void setComponent(AppComponent appComponent) {
                ComponentHolder.setAppComponent(appComponent);
            }
        });
    }
}复制代码

怎么样,很简单吧?这个DaggerRule是能够重复使用的,通常来讲,一个Component类对应于一个这样的DaggerRule就行了。自此,你能够只负责使用@Mock来定义mock了,dagger的事情就交给这个DaggerRule就行了。
是否是很爽!工具

哦对了,将这个library加到项目里面的姿式说一下,在build.gradle文件里面加入:单元测试

repositories {
    jcenter()
    maven { url "https://jitpack.io" }
}复制代码

测试

dependencies {
    //...others dependencies

    testCompile 'com.github.fabioCollini:DaggerMock:0.6.1'
    androidTestCompile 'com.github.fabioCollini:DaggerMock:0.6.1' //若是你须要在Instrumentation、Espresso、UiAutomator里面使用的话
}复制代码

我刚开始使用这个lib的时候,仍是花了点时间来理解的,我的认为做者的README和对应的文章写得都不算是很容易看懂,但愿这篇文章能让帮助到各位一点点。

照例文中的代码在github的这个repo

获取最新文章或想加入安卓单元测试交流群,请关注下方公众号

相关文章
相关标签/搜索