咱們在系列的第六篇文章前面介紹了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
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
@Mock
field爲mock對象(loginPresenter
)AppModule
,經過反射的方式獲得AppModule
的全部provider方法,若是有某個方法的返回值是一個LoginPresenter
,那麼就使用Mockito,讓這個方法(provideLoginPresenter(...)
)被調用時,返回咱們在測試類裏面定義的mock loginPresenter
。ComponentHolder
裏面去。我相信看到這裏,你必定有不少疑問:maven
AppModule
好吧,其實上面的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。
獲取最新文章或想加入安卓單元測試交流羣,請關注下方公衆號