Testing Android apps based on Dagger and RxJava
-
Upload
fabio-collini -
Category
Internet
-
view
304 -
download
0
Transcript of Testing Android apps based on Dagger and RxJava
Testing Android apps based on Dagger and RxJava
Fabio Collini
droidcon Italy April 2017
2
Ego slide
@fabioCollini linkedin.com/in/fabiocollini github.com/fabioCollini medium.com/@fabioCollini codingjam.it
3
Agenda
1. Dagger 2. Testing 3. Mockito 4. Espresso 5. DaggerMock 6. RxJava
4github.com/fabioCollini/TestingDaggerRxJava
5
Activity
Presenter
Interactor
Retrofit Service
Rx
Rx
@Si
ngle
ton
@Pr
ovid
es@
Sing
leto
n@
Prov
ides
@Pr
ovid
es@
Inje
ct
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
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
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
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
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() { //... } }
Activity
Presenter
Interactor
Retrofit Service
Unit
Unit
Unit
Inte
grat
ion
UI
UI
UI
E2E
Espresso JVM
12
Return Of Investment
Net profit Investment
13
14
15
16
Unit
E2E
Integration
Manual tests
Testing pyramid
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
18
Mockito tips
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); }_ }__
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); }_ }__
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); }}
Espr
esso
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 ); }}
Activity
Presenter
Interactor
Retrofit Service
@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); }}
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
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
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 ); }__ }_
Scheduler asyncTaskScheduler = Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR);
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(); } } }; }}
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
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
@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);}__ //...
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);}__ //...
34
//... @Rule public DaggerMockRule<ApplicationComponent> daggerMockRule = new DaggerMockRule<>( ApplicationComponent.class, new UserInteractorModule() ).set(component -> getAppFromInstrumentation().setComponent(component));@Mock UserInteractor userInteractor; //...
Activity
Presenter
Interactor
Retrofit Service
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"))); }}
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
Activity
Presenter
Interactor
Retrofit Service
@Rule public DaggerMockRule<ApplicationComponent> daggerMockRule = new DaggerMockRule<>( ApplicationComponent.class, new UserInteractorModule() ).set(component -> getAppFromInstrumentation().setComponent(component));
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()) ); }__ }_
Espr
esso
Activity
Presenter
Interactor
Retrofit Service
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
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
Activity
Presenter
Interactor
Retrofit Service
JVM
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
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 ); }_
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
47
48
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(); } } }; }}
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"); }__ }_
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"); }__ }_
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"); }__ }_
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
Activity
Presenter
Interactor
Retrofit Service
JVM
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(); }}
56
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
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
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(); }_
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(); }_
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") ); }__ }_
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") ); }__ }_
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") ); }__ }_
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") )_ ));
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") )_ ));
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") )_ ));
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(); } } }; }}
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") )_ ));
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
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(); }_ }__
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(); }_ }__
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
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(); }_ }__
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") )_ ));
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
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
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
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/
Thanks for your attention!
Questions?