79
Testing Android apps based on Dagger and RxJava Fabio Collini droidcon Italy April 2017

Testing Android apps based on Dagger and RxJava

Embed Size (px)

Citation preview

Page 1: Testing Android apps based on Dagger and RxJava

Testing Android apps based on Dagger and RxJava

Fabio Collini

droidcon Italy April 2017

Page 2: Testing Android apps based on Dagger and RxJava

2

Ego slide

@fabioCollini linkedin.com/in/fabiocollini github.com/fabioCollini medium.com/@fabioCollini codingjam.it

Page 3: Testing Android apps based on Dagger and RxJava

3

Agenda

1. Dagger 2. Testing 3. Mockito 4. Espresso 5. DaggerMock 6. RxJava

Page 4: Testing Android apps based on Dagger and RxJava

4github.com/fabioCollini/TestingDaggerRxJava

Page 5: Testing Android apps based on Dagger and RxJava

5

Activity

Presenter

Interactor

Retrofit Service

Rx

Rx

@Si

ngle

ton

@Pr

ovid

es@

Sing

leto

n@

Prov

ides

@Pr

ovid

es@

Inje

ct

Page 6: Testing Android apps based on Dagger and RxJava

public interface StackOverflowService { @GET("/users") Single<List<User>> getTopUsers(); @GET("/users/{userId}/badges") Single<List<Badge>> getBadges(@Path("userId") int userId);}

Activity

Presenter

Interactor

Retrofit Service

Rx

Rx

Sing

leto

nSi

ngle

ton

Page 7: Testing Android apps based on Dagger and RxJava

public class UserInteractor { private StackOverflowService service; public UserInteractor(StackOverflowService service) { this.service = service; } public Single<List<UserStats>> loadUsers() { //... } }

Activity

Presenter

Interactor

Retrofit Service

Rx

Rx

Sing

leto

nSi

ngle

ton

Page 8: Testing Android apps based on Dagger and RxJava

public class UserListPresenter {b private UserInteractor userInteractor; private UserListActivity activity; public UserListPresenter(UserInteractor userInteractor, UserListActivity activity) { this.userInteractor = userInteractor; this.activity = activity; }onCreate public void reloadUserList() { userInteractor .loadUsers() //... .subscribe( activity::updateText, activity::showError ); }showError}end

Activity

Presenter

Interactor

Retrofit Service

Rx

Rx

Sing

leto

nSi

ngle

ton

Page 9: Testing Android apps based on Dagger and RxJava

public class UserListActivity extends AppCompatActivity {b @Inject UserListPresenter presenter; @Override protected void onCreate(Bundle savedInstanceState) { //... ((MyApp) getApplicationContext()).getComponent() .userListComponent(new UserListModule(this)).inject(this); presenter.reloadUserList(); }onCreate public void updateText(String s) { //... }updateText public void showError(Throwable t) { //... }showError}end

Activity

Presenter

Interactor

Retrofit Service

Rx

Rx

Sing

leto

nSi

ngle

ton

Page 10: Testing Android apps based on Dagger and RxJava

10

@Singleton@Component(modules = {UserInteractorModule.class, StackOverflowServiceModule.class})public interface ApplicationComponent { UserListComponent userListComponent(UserListModule module);}

@Modulepublic class UserInteractorModule { @Provides @Singleton public UserInteractor provideInteractor() { //... } } @Module

public class StackOverflowServiceModule { @Provides @Singleton public StackOverflowService provideService() { //... } }

@Subcomponent(modules = UserListModule.class) public interface UserListComponent { void inject(UserListActivity activity);}

@Modulepublic class UserListModule { @Provides public UserListPresenter providePresenter() { //... } }

Page 11: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

Unit

Unit

Unit

Inte

grat

ion

UI

UI

UI

E2E

Espresso JVM

Page 12: Testing Android apps based on Dagger and RxJava

12

Return Of Investment

Net profit Investment

Page 13: Testing Android apps based on Dagger and RxJava

13

Page 14: Testing Android apps based on Dagger and RxJava

14

Page 15: Testing Android apps based on Dagger and RxJava

15

Page 16: Testing Android apps based on Dagger and RxJava

16

Unit

E2E

Integration

Manual tests

Testing pyramid

Page 17: Testing Android apps based on Dagger and RxJava

17

Integrated tests are a scam a self-replicating virus that threatens to infect your code base, your project, and your team with endless

pain and suffering. J. B. Rainsberger

Page 18: Testing Android apps based on Dagger and RxJava

18

Mockito tips

Page 19: Testing Android apps based on Dagger and RxJava

public class MyTest { Collaborator1 collaborator1; Collaborator2 collaborator2; ObjectUnderTest objectUnderTest; @Before public void setUp() { collaborator1 = Mockito.mock(Collaborator1.class); collaborator2 = Mockito.mock(Collaborator2.class); objectUnderTest = new ObjectUnderTest( collaborator1, collaborator2); }setUp @Test public void myTestMethod() { //Arrange when(collaborator1.provideValue()).thenReturn(2); //Act objectUnderTest.execute(); //Assert verify(collaborator2).printValue(10); assertThat(objectUnderTest.getValue()).isEqualTo(10); }_ }__

Page 20: Testing Android apps based on Dagger and RxJava

public class MyTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock Collaborator1 collaborator1; @Mock Collaborator2 collaborator2; @InjectMocks ObjectUnderTest objectUnderTest; @Test public void myTestMethod() { //Arrange when(collaborator1.provideValue()).thenReturn(2); //Act objectUnderTest.execute(); //Assert verify(collaborator2).printValue(10); assertThat(objectUnderTest.getValue()).isEqualTo(10); }_ }__

Page 21: Testing Android apps based on Dagger and RxJava

package org.mockito.configuration;

//...

public class MockitoConfiguration extends DefaultMockitoConfiguration { public Answer<Object> getDefaultAnswer() { return new ReturnsEmptyValues() { @Override public Object answer(InvocationOnMock inv) { Class<?> type = inv.getMethod().getReturnType(); if (type.isAssignableFrom(Observable.class)) { return Observable.error(createException(inv)); } else if (type.isAssignableFrom(Single.class)) { return Single.error(createException(inv)); } else { return super.answer(inv); } } }; } private RuntimeException createException( InvocationOnMock invocation) { String s = invocation.toString(); return new RuntimeException( "No mock defined for invocation " + s); }}

Page 22: Testing Android apps based on Dagger and RxJava

Espr

esso

Activity

Presenter

Interactor

Retrofit Service

Page 23: Testing Android apps based on Dagger and RxJava

public class UserListPresenter { private UserInteractor userInteractor; private UserListActivity activity; public UserListPresenter(UserInteractor userInteractor, UserListActivity activity) { this.userInteractor = userInteractor; this.activity = activity; } public void reloadUserList() { userInteractor .loadUsers() .flattenAsObservable(l -> l) .map(UserStats::toString) .reduce((s1, s2) -> s1 + "\n\n" + s2) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( activity::updateText, activity::showError ); }}

Activity

Presenter

Interactor

Retrofit Service

Page 24: Testing Android apps based on Dagger and RxJava

@Singleton@Component(modules = { TestUserInteractorModule.class, StackOverflowServiceModule.class}) public interface TestApplicationComponent extends ApplicationComponent { void inject(UserListActivityTest userListActivityTest);}

Activity

Presenter

Interactor

Retrofit Service

@Modulepublic class TestUserInteractorModule { @Provides @Singleton public UserInteractor provideUserInteractor() { return Mockito.mock(UserInteractor.class); }}

Page 25: Testing Android apps based on Dagger and RxJava

public class UserListActivityTest { @Rule public ActivityTestRule<UserListActivity> rule = new ActivityTestRule<>(UserListActivity.class, false, false); @Inject UserInteractor userInteractor; @Before public void setUp() { TestApplicationComponent component = DaggerTestApplicationComponent.create(); getAppFromInstrumentation().setComponent(component); component.inject(this); }A @Test public void shouldDisplayUsers() { when(userInteractor.loadUsers()).thenReturn( Observable.fromArray( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") ).toList()); rule.launchActivity(null); onView(withId(R.id.text)).check(matches(withText( "50 user1 - badge1\n\n30 user2 - badge2, badge3"))); }B }C

Activity

Presenter

Interactor

Retrofit Service

Page 26: Testing Android apps based on Dagger and RxJava

public class UserListActivityTest { @Rule public ActivityTestRule<UserListActivity> rule = new ActivityTestRule<>(UserListActivity.class, false, false); @Inject UserInteractor userInteractor; @Before public void setUp() { TestApplicationComponent component = DaggerTestApplicationComponent.create(); getAppFromInstrumentation().setComponent(component); component.inject(this); }A @Test public void shouldDisplayUsers() { when(userInteractor.loadUsers()).thenReturn( Observable.fromArray( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") ).toList()); rule.launchActivity(null); onView(withId(R.id.text)).check(matches(withText( "50 user1 - badge1\n\n30 user2 - badge2, badge3"))); }B }C

Activity

Presenter

Interactor

Retrofit Service

Arrange Act Assert

Page 27: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

public class UserListPresenter { private UserInteractor userInteractor; private UserListActivity activity; public UserListPresenter(UserInteractor userInteractor, UserListActivity activity) { this.userInteractor = userInteractor; this.activity = activity; }___ public void reloadUserList() { userInteractor .loadUsers() .flattenAsObservable(l -> l) .map(UserStats::toString) .reduce((s1, s2) -> s1 + "\n\n" + s2) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( activity::updateText, activity::showError ); }__ }_

Page 28: Testing Android apps based on Dagger and RxJava

Scheduler asyncTaskScheduler = Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR);

Page 29: Testing Android apps based on Dagger and RxJava

public class AsyncTaskSchedulerRule implements TestRule { private final Scheduler asyncTaskScheduler = Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR); @Override public Statement apply(final Statement base, Description d) { return new Statement() { @Override public void evaluate() throws Throwable { RxJavaPlugins.setIoSchedulerHandler( scheduler -> asyncTaskScheduler); RxJavaPlugins.setComputationSchedulerHandler( scheduler -> asyncTaskScheduler); RxJavaPlugins.setNewThreadSchedulerHandler( scheduler -> asyncTaskScheduler); try { base.evaluate(); } finally { RxJavaPlugins.reset(); } } }; }}

Page 30: Testing Android apps based on Dagger and RxJava

public class UserListActivityTest { @Rule public ActivityTestRule<UserListActivity> rule = new ActivityTestRule<>(UserListActivity.class, false, false);

@Rule public AsyncTaskSchedulerRule asyncTaskSchedulerRule = new AsyncTaskSchedulerRule(); @Inject UserInteractor userInteractor; @Before public void setUp() { TestApplicationComponent component = DaggerTestApplicationComponent.create(); getAppFromInstrumentation().setComponent(component); component.inject(this); }A @Test public void shouldDisplayUsers() { when(userInteractor.loadUsers()).thenReturn( Observable.fromArray( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") ).toList()); rule.launchActivity(null); onView(withId(R.id.text)).check(matches(withText( "50 user1 - badge1\n\n30 user2 - badge2, badge3"))); }B }C

Activity

Presenter

Interactor

Retrofit Service

Arrange Act Assert

Page 31: Testing Android apps based on Dagger and RxJava

public class UserListActivityTest { @Rule public ActivityTestRule<UserListActivity> rule = new ActivityTestRule<>(UserListActivity.class, false, false);

@Rule public AsyncTaskSchedulerRule asyncTaskSchedulerRule = new AsyncTaskSchedulerRule(); @Inject UserInteractor userInteractor; @Before public void setUp() { TestApplicationComponent component = DaggerTestApplicationComponent.create(); getAppFromInstrumentation().setComponent(component); component.inject(this); }A @Test public void shouldDisplayUsers() { when(userInteractor.loadUsers()).thenReturn( Observable.fromArray( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") ).toList()); rule.launchActivity(null); onView(withId(R.id.text)).check(matches(withText( "50 user1 - badge1\n\n30 user2 - badge2, badge3"))); }B }C

Activity

Presenter

Interactor

Retrofit Service

Arrange Act Assert

Page 32: Testing Android apps based on Dagger and RxJava

@Singleton@Component(modules = { TestUserInteractorModule.class, StackOverflowServiceModule.class}) public interface TestApplicationComponent extends ApplicationComponent { void inject(UserListActivityTest userListActivityTest);}_

Activity

Presenter

Interactor

Retrofit Service

@Modulepublic class TestUserInteractorModule { @Provides @Singleton public UserInteractor provideUserInteractor() { return Mockito.mock(UserInteractor.class); }}

//...@Inject UserInteractor userInteractor; @Before public void setUp() { TestApplicationComponent component = DaggerTestApplicationComponent.create(); getAppFromInstrumentation().setComponent(component); component.inject(this);}__ //...

Page 33: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

//... @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();@Mock UserInteractor userInteractor; @Before public void setUp() { ApplicationComponent component = DaggerApplicationComponent.builder() .userInteractorModule(new UserInteractorModule() { @Override public UserInteractor provideUserInteractor( StackOverflowService service) { return userInteractor; }___ }) .build(); getAppFromInstrumentation().setComponent(component);}__ //...

Page 34: Testing Android apps based on Dagger and RxJava

34

Page 35: Testing Android apps based on Dagger and RxJava

//... @Rule public DaggerMockRule<ApplicationComponent> daggerMockRule = new DaggerMockRule<>( ApplicationComponent.class, new UserInteractorModule() ).set(component -> getAppFromInstrumentation().setComponent(component));@Mock UserInteractor userInteractor; //...

Activity

Presenter

Interactor

Retrofit Service

Page 36: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

public class UserListActivityTest { @Rule public ActivityTestRule<UserListActivity> rule = new ActivityTestRule<>(UserListActivity.class, false, false); @Rule public AsyncTaskSchedulerRule asyncTaskSchedulerRule = new AsyncTaskSchedulerRule(); @Rule public DaggerMockRule<ApplicationComponent> daggerMockRule = new DaggerMockRule<>( ApplicationComponent.class, new UserInteractorModule() ).set(component -> getAppFromInstrumentation().setComponent(component)); @Mock UserInteractor userInteractor; @Test public void shouldDisplayUsers() { when(userInteractor.loadUsers()).thenReturn( Observable.fromArray( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") ).toList()); rule.launchActivity(null); onView(withId(R.id.text)).check(matches(withText( "50 user1 - badge1\n\n30 user2 - badge2, badge3"))); }}

Page 37: Testing Android apps based on Dagger and RxJava

static <T> T createSubclass(final T module, final Map<Class, Object> testFields) { return (T) Mockito.mock(module.getClass(), new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Method method = invocation.getMethod(); Object obj = testFields.get(method.getReturnType()); if (obj != null) { return obj; } else { return method.invoke(module, invocation.getArguments()); } } });}

DaggerMock internals

Page 38: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

@Rule public DaggerMockRule<ApplicationComponent> daggerMockRule = new DaggerMockRule<>( ApplicationComponent.class, new UserInteractorModule() ).set(component -> getAppFromInstrumentation().setComponent(component));

Page 39: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

public class MyDaggerMockRule extends DaggerMockRule<ApplicationComponent> { public MyDaggerMockRule() { super( ApplicationComponent.class, new UserInteractorModule() ); set(component -> getAppFromInstrumentation().setComponent(component)); providesMock(UserInteractor.class, userInteractor -> when(userInteractor.loadUsers()).thenReturn( Observable.fromArray( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") ).toList()) ); }__ }_

Page 40: Testing Android apps based on Dagger and RxJava

Espr

esso

Activity

Presenter

Interactor

Retrofit Service

Page 41: Testing Android apps based on Dagger and RxJava

41

public class MockPresenterTest { @Rule public ActivityTestRule<UserListActivity> rule = new ActivityTestRule<>(UserListActivity.class, false, false); @Rule public MyDaggerMockRule daggerMockRule = new MyDaggerMockRule(); @Mock UserListPresenter presenter; @Test public void testOnCreate() { rule.launchActivity(null); onView(withId(R.id.text)).check(matches(withText(""))); verify(presenter).reloadUserList(); }}

Activity

Presenter

Interactor

Retrofit Service

Page 42: Testing Android apps based on Dagger and RxJava

1.void method that uses RxJava schedulers

2.method that returns a synchronous RxJava object

3.method that returns an asynchronous RxJava object

Testing RxJava code

Page 43: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

JVM

Page 44: Testing Android apps based on Dagger and RxJava

public class UserListPresenter { private UserInteractor userInteractor; private UserListActivity activity; public UserListPresenter(UserInteractor userInteractor, UserListActivity activity) { this.userInteractor = userInteractor; this.activity = activity; } public void reloadUserList() { userInteractor .loadUsers() .flattenAsObservable(l -> l) .map(UserStats::toString) .reduce((s1, s2) -> s1 + "\n\n" + s2) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( activity::updateText, activity::showError ); }_ }

Activity

Presenter

Interactor

Retrofit Service

Page 45: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

public void reloadUserList() { userInteractor .loadUsers() .flattenAsObservable(l -> l) .map(UserStats::toString) .reduce((s1, s2) -> s1 + "\n\n" + s2) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( activity::updateText, activity::showError ); }_

Page 46: Testing Android apps based on Dagger and RxJava

public class UserListPresenterTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock UserInteractor userInteractor; @Mock UserListActivity activity; @InjectMocks UserListPresenter presenter; @Test public void shouldLoadUsers() { when(userInteractor.loadUsers()).thenReturn( Observable.fromArray( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") ).toList());

presenter.reloadUserList();

verify(activity).updateText(anyString()); }_ }__

Activity

Presenter

Interactor

Retrofit Service

Page 47: Testing Android apps based on Dagger and RxJava

47

Page 48: Testing Android apps based on Dagger and RxJava

48

Page 49: Testing Android apps based on Dagger and RxJava

public class TrampolineSchedulerRule implements TestRule { @Override public Statement apply(final Statement base, Description d) { return new Statement() { @Override public void evaluate() throws Throwable { RxJavaPlugins.setIoSchedulerHandler( scheduler -> Schedulers.trampoline()); RxJavaPlugins.setComputationSchedulerHandler( scheduler -> Schedulers.trampoline()); RxJavaPlugins.setNewThreadSchedulerHandler( scheduler -> Schedulers.trampoline()); RxAndroidPlugins.setInitMainThreadSchedulerHandler( scheduler -> Schedulers.trampoline()); try { base.evaluate(); } finally { RxJavaPlugins.reset(); RxAndroidPlugins.reset(); } } }; }}

Page 50: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

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

@Rule public TrampolineSchedulerRule schedulerRule = new TrampolineSchedulerRule(); @Mock UserInteractor userInteractor; @Mock UserListActivity activity; @InjectMocks UserListPresenter presenter; @Test public void shouldLoadUsers() { when(userInteractor.loadUsers()).thenReturn( Observable.fromArray( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") ).toList()); presenter.reloadUserList(); verify(activity).updateText( "50 user1 - badge1\n\n30 user2 - badge2, badge3"); }__ }_

Page 51: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

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

@Rule public TrampolineSchedulerRule schedulerRule = new TrampolineSchedulerRule(); @Mock UserInteractor userInteractor; @Mock UserListActivity activity; @InjectMocks UserListPresenter presenter; @Test public void shouldLoadUsers() { when(userInteractor.loadUsers()).thenReturn( Observable.fromArray( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") ).toList()); presenter.reloadUserList(); verify(activity).updateText( "50 user1 - badge1\n\n30 user2 - badge2, badge3"); }__ }_

Page 52: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

public class UserListPresenterTest { @Rule public DaggerMockRule<ApplicationComponent> daggerMockRule = new DaggerMockRule<>(ApplicationComponent.class, new UserInteractorModule()); @Rule public TrampolineSchedulerRule schedulerRule = new TrampolineSchedulerRule(); @Mock UserInteractor userInteractor; @Mock UserListActivity activity; @InjectFromComponent(UserListActivity.class) UserListPresenter presenter; @Test public void shouldLoadUsers() { when(userInteractor.loadUsers()).thenReturn( Observable.fromArray( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") ).toList()); presenter.reloadUserList(); verify(activity).updateText( "50 user1 - badge1\n\n30 user2 - badge2, badge3"); }__ }_

Page 53: Testing Android apps based on Dagger and RxJava

1.void method that uses RxJava schedulers

2.method that returns a synchronous RxJava object

3.method that returns an asynchronous RxJava object

Testing RxJava code

trampoline scheduler

Page 54: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

JVM

Page 55: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

public class UserInteractor { private StackOverflowService service; public UserInteractor(StackOverflowService service) { this.service = service; } public Single<List<UserStats>> loadUsers() { return service.getTopUsers() .flattenAsObservable(l -> l) .take(5) .flatMap(user -> service.getBadges(user.id()) .map(badges -> UserStats.create(user, badges)) .toObservable() )____ .toList(); }}

Page 56: Testing Android apps based on Dagger and RxJava

56

Page 57: Testing Android apps based on Dagger and RxJava

public class UserInteractorTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock StackOverflowService stackOverflowService; @InjectMocks UserInteractor userInteractor; @Test public void shouldLoadUsers() { when(stackOverflowService.getTopUsers()).thenReturn( Observable.fromArray( User.create(1, 50, "user1"), User.create(2, 30, "user2") ).toList()); when(stackOverflowService.getBadges(1)).thenReturn( Single.just(Badge.createList("badge1"))); when(stackOverflowService.getBadges(2)).thenReturn( Single.just(Badge.createList("badge2", "badge3")));

List<UserStats> users = userInteractor.loadUsers().blockingGet(); assertThat(users).containsExactly( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") ); }__ }_

Activity

Presenter

Interactor

Retrofit Service

Page 58: Testing Android apps based on Dagger and RxJava

1.void method that uses RxJava schedulers

2.method that returns a synchronous RxJava object

3.method that returns an asynchronous RxJava object

Testing RxJava code

trampoline scheduler

blockingGet

Page 59: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

public Single<List<UserStats>> loadUsers() { return service.getTopUsers() .flattenAsObservable(l -> l) .take(5) .flatMap(user -> service.getBadges(user.id()) .map(badges -> UserStats.create(user, badges)) .toObservable() )____ .toList(); }_

Page 60: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

public Single<List<UserStats>> loadUsers() { return service.getTopUsers() .flattenAsObservable(l -> l) .take(5) .flatMap(user -> service.getBadges(user.id()) .subscribeOn(Schedulers.io()) .map(badges -> UserStats.create(user, badges)) .toObservable() )____ .toList(); }_

Page 61: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

public class UserInteractorTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock StackOverflowService stackOverflowService; @InjectMocks UserInteractor userInteractor; @Test public void shouldLoadUsers() { when(stackOverflowService.getTopUsers()).thenReturn( Observable.fromArray( User.create(1, 50, "user1"), User.create(2, 30, "user2") ).toList()); when(stackOverflowService.getBadges(1)).thenReturn( Single.just(Badge.createList("badge1"))); when(stackOverflowService.getBadges(2)).thenReturn( Single.just(Badge.createList("badge2", "badge3")));

List<UserStats> users = userInteractor.loadUsers().blockingGet(); assertThat(users).containsExactly( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") ); }__ }_

Page 62: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

public class UserInteractorTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock StackOverflowService stackOverflowService; @InjectMocks UserInteractor userInteractor; @Test public void shouldLoadUsers() { when(stackOverflowService.getTopUsers()).thenReturn( Observable.fromArray( User.create(1, 50, "user1"), User.create(2, 30, "user2") ).toList()); when(stackOverflowService.getBadges(1)).thenReturn( Single.just(Badge.createList("badge1"))); when(stackOverflowService.getBadges(2)).thenReturn( Single.just(Badge.createList("badge2", "badge3")));

List<UserStats> users = userInteractor.loadUsers().blockingGet(); assertThat(users).containsExactly( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") ); }__ }_

Page 63: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

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

@Rule public TrampolineSchedulerRule schedulerRule = new TrampolineSchedulerRule(); @Mock StackOverflowService stackOverflowService; @InjectMocks UserInteractor userInteractor; @Test public void shouldLoadUsers() { when(stackOverflowService.getTopUsers()).thenReturn( Observable.fromArray( User.create(1, 50, "user1"), User.create(2, 30, "user2") ).toList()); when(stackOverflowService.getBadges(1)).thenReturn( Single.just(Badge.createList("badge1"))); when(stackOverflowService.getBadges(2)).thenReturn( Single.just(Badge.createList("badge2", "badge3")));

List<UserStats> users = userInteractor.loadUsers().blockingGet(); assertThat(users).containsExactly( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") ); }__ }_

Page 64: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

when(stackOverflowService.getTopUsers()).thenReturn( Observable.fromArray( User.create(1, 50, "user1"), User.create(2, 30, "user2") ).toList());when(stackOverflowService.getBadges(1)).thenReturn( Single.just(Badge.createList("badge1")) .delay(2, TimeUnit.SECONDS));when(stackOverflowService.getBadges(2)).thenReturn( Single.just(Badge.createList("badge2", "badge3")) .delay(1, TimeUnit.SECONDS));userInteractor.loadUsers() .test() .assertNoErrors() .assertValue(check(users -> assertThat(users).containsExactly( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") )_ ));

Page 65: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

when(stackOverflowService.getTopUsers()).thenReturn( Observable.fromArray( User.create(1, 50, "user1"), User.create(2, 30, "user2") ).toList());when(stackOverflowService.getBadges(1)).thenReturn( Single.just(Badge.createList("badge1")) .delay(2, TimeUnit.SECONDS));when(stackOverflowService.getBadges(2)).thenReturn( Single.just(Badge.createList("badge2", "badge3")) .delay(1, TimeUnit.SECONDS));userInteractor.loadUsers() .test() .assertNoErrors() .assertValue(check(users -> assertThat(users).containsExactly( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") )_ ));

Page 66: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

when(stackOverflowService.getTopUsers()).thenReturn( Observable.fromArray( User.create(1, 50, "user1"), User.create(2, 30, "user2") ).toList());when(stackOverflowService.getBadges(1)).thenReturn( Single.just(Badge.createList("badge1")) .delay(2, TimeUnit.SECONDS));when(stackOverflowService.getBadges(2)).thenReturn( Single.just(Badge.createList("badge2", "badge3")) .delay(1, TimeUnit.SECONDS));userInteractor.loadUsers() .test() .assertNoErrors() .assertValue(check(users -> assertThat(users).containsExactly( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") )_ ));

Page 67: Testing Android apps based on Dagger and RxJava

public class TestSchedulerRule implements TestRule { private final TestScheduler testScheduler = new TestScheduler(); public TestScheduler getTestScheduler() { return testScheduler; } @Override public Statement apply(final Statement base, Description d) { return new Statement() { @Override public void evaluate() throws Throwable { RxJavaPlugins.setIoSchedulerHandler( scheduler -> testScheduler); RxJavaPlugins.setComputationSchedulerHandler( scheduler -> testScheduler); RxJavaPlugins.setNewThreadSchedulerHandler( scheduler -> testScheduler); RxAndroidPlugins.setMainThreadSchedulerHandler( scheduler -> Schedulers.trampoline()); try { base.evaluate(); } finally { RxJavaPlugins.reset(); RxAndroidPlugins.reset(); } } }; }}

Page 68: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

when(stackOverflowService.getTopUsers()).thenReturn( Observable.fromArray( User.create(1, 50, "user1"), User.create(2, 30, "user2") ).toList());when(stackOverflowService.getBadges(1)).thenReturn( Single.just(Badge.createList("badge1")) .delay(2, TimeUnit.SECONDS));when(stackOverflowService.getBadges(2)).thenReturn( Single.just(Badge.createList("badge2", "badge3")) .delay(1, TimeUnit.SECONDS));TestObserver<List<UserStats>> testObserver = userInteractor.loadUsers().test();schedulerRule.getTestScheduler() .advanceTimeBy(2, TimeUnit.SECONDS);testObserver .assertNoErrors() .assertValue(check(users -> assertThat(users).containsExactly( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") )_ ));

Page 69: Testing Android apps based on Dagger and RxJava

when(stackOverflowService.getTopUsers()).thenReturn( Observable.fromArray( User.create(1, 50, "user1"), User.create(2, 30, "user2") ).toList());when(stackOverflowService.getBadges(1)).thenReturn( Single.just(Badge.createList("badge1")) .delay(2, TimeUnit.SECONDS));when(stackOverflowService.getBadges(2)).thenReturn( Single.just(Badge.createList("badge2", "badge3")) .delay(1, TimeUnit.SECONDS));TestObserver<List<UserStats>> testObserver = userInteractor.loadUsers().test();schedulerRule.getTestScheduler() .advanceTimeBy(2, TimeUnit.SECONDS);testObserver .assertNoErrors() .assertValue(check(users -> assertThat(users).containsExactly( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") )_ ));

Activity

Presenter

Interactor

Retrofit Service

Page 70: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

public class UserInteractor { private StackOverflowService service; public UserInteractor(StackOverflowService service) { this.service = service; }___ public Single<List<UserStats>> loadUsers() { return service.getTopUsers() .flattenAsObservable(l -> l) .take(5) .flatMap(user -> service.getBadges(user.id()) .subscribeOn(Schedulers.io()) .map(badges -> UserStats.create(user, badges)) .toObservable() )____ .toList(); }_ }__

Page 71: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

public class UserInteractor { private StackOverflowService service; public UserInteractor(StackOverflowService service) { this.service = service; }___ public Single<List<UserStats>> loadUsers() { return service.getTopUsers() .flattenAsObservable(l -> l) .take(5) .flatMap(user -> service.getBadges(user.id()) .subscribeOn(Schedulers.io()) .map(badges -> UserStats.create(user, badges)) .toObservable() )____ .toList(); }_ }__

Page 72: Testing Android apps based on Dagger and RxJava

public class UserInteractor { private StackOverflowService service; public UserInteractor(StackOverflowService service) { this.service = service; }___ public Single<List<UserStats>> loadUsers() { return service.getTopUsers() .flattenAsObservable(l -> l) .take(5) .flatMap(user -> service.getBadges(user.id()) .subscribeOn(Schedulers.io()) .map(badges -> UserStats.create(user, badges)) .toObservable() )____ .toList(); }_ }__

Activity

Presenter

Interactor

Retrofit Service

Page 73: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

public class UserInteractor { private StackOverflowService service; public UserInteractor(StackOverflowService service) { this.service = service; }___ public Single<List<UserStats>> loadUsers() { return service.getTopUsers() .flattenAsObservable(l -> l) .take(5) .concatMapEager(user -> service.getBadges(user.id()) .subscribeOn(Schedulers.io()) .map(badges -> UserStats.create(user, badges)) .toObservable() )____ .toList(); }_ }__

Page 74: Testing Android apps based on Dagger and RxJava

Activity

Presenter

Interactor

Retrofit Service

when(stackOverflowService.getTopUsers()).thenReturn( Observable.fromArray( User.create(1, 50, "user1"), User.create(2, 30, "user2") ).toList());when(stackOverflowService.getBadges(1)).thenReturn( Single.just(Badge.createList("badge1")) .delay(2, TimeUnit.SECONDS));when(stackOverflowService.getBadges(2)).thenReturn( Single.just(Badge.createList("badge2", "badge3")) .delay(1, TimeUnit.SECONDS));TestObserver<List<UserStats>> testObserver = userInteractor.loadUsers().test();schedulerRule.getTestScheduler() .advanceTimeBy(2, TimeUnit.SECONDS);testObserver .assertNoErrors() .assertValue(check(l -> assertThat(l).containsExactly( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") )_ ));

Page 75: Testing Android apps based on Dagger and RxJava

when(stackOverflowService.getTopUsers()).thenReturn( Observable.fromArray( User.create(1, 50, "user1"), User.create(2, 30, "user2") ).toList());when(stackOverflowService.getBadges(1)).thenReturn( Single.just(Badge.createList("badge1")) .delay(2, TimeUnit.SECONDS));when(stackOverflowService.getBadges(2)).thenReturn( Single.just(Badge.createList("badge2", "badge3")) .delay(1, TimeUnit.SECONDS));TestObserver<List<UserStats>> testObserver = userInteractor.loadUsers().test();schedulerRule.getTestScheduler() .advanceTimeBy(2, TimeUnit.SECONDS);testObserver .assertNoErrors() .assertValue(check(l -> assertThat(l).containsExactly( UserStats.create(1, 50, "user1", "badge1"), UserStats.create(2, 30, "user2", "badge2", "badge3") )_ ));

Activity

Presenter

Interactor

Retrofit Service

Page 76: Testing Android apps based on Dagger and RxJava

1.void method that uses RxJava schedulers

2.method that returns a synchronous RxJava object

3.method that returns an asynchronous RxJava object

Testing RxJava code

trampoline scheduler

blockingGet

TestScheduler & TestObserver

Page 77: Testing Android apps based on Dagger and RxJava

77

Wrapping up

1.JVM tests isolate failures and are fast and reliable

2.Using DaggerMock testing boilerplate code can be reduced

3.RxJava asynchronous code can be tested using TestObserver and TestScheduler

Page 78: Testing Android apps based on Dagger and RxJava

78

Linksgithub.com/fabioCollini/TestingDaggerRxJava github.com/fabioCollini/DaggerMock joel-costigliola.github.io/assertj

medium.com/@fabioCollini/testing-asynchronous-rxjava-code-using-mockito-8ad831a16877

medium.com/@fabioCollini/android-testing-using-dagger-2-mockito-and-a-custom-junit-rule-c8487ed01b56

testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html blog.thecodewhisperer.com/permalink/clearing-up-the-integrated-tests-scam

collectiveidea.com/blog/archives/2016/10/13/retrofitting-espresso/ medium.com/@peter.tackage/an-alternative-to-rxandroidplugins-and-rxjavaplugins-

scheduler-injection-9831bbc3dfaf artemzin.com/blog/jfyi-overriding-module-classes-with-dagger2/

Page 79: Testing Android apps based on Dagger and RxJava

Thanks for your attention!

Questions?