Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Testing Android apps based on Dagger and RxJava Droidcon UK

977 views

Published on

Writing reliable tests is not easy for many reasons, especially when asynchronous code is involved. Some libraries can be used to create a testable architecture (for example Dagger and RxJava) and to simplify testing (Mockito, AssertJ and DaggerMock). In this talk you'll learn how to write JVM and Espresso tests with particular attention on how to replace real objects with mocks and how to test asynchronous RxJava code. In this talk, you will also explore:

how to take advantage of Mockito and other libraries to write good JVM and Espresso tests in both Java and Kotlin
how to use DaggerMock (an open source library available on github to avoid boilerplate code in tests
how to test asynchronous RxJava code using a JVM test

Published in: Software
  • Be the first to comment

Testing Android apps based on Dagger and RxJava Droidcon UK

  1. 1. Testing Android apps based on Dagger and RxJava Fabio Collini droidcon London October 2017
  2. 2. Ego slide @fabioCollini linkedin.com/in/fabiocollini github.com/fabioCollini medium.com/@fabioCollini codingjam.it Android programmazione avanzata
  3. 3. Agenda 1. How to use Dagger to replace objects with test doubles 2. How to test asynchronous RxJava code 3. How to use Kotlin to simplify tests
  4. 4. github.com/fabioCollini/TestingDaggerRxJava
  5. 5. Activity Presenter Interactor Retrofit Service
  6. 6. Rx Activity Presenter Interactor Retrofit Service Rx @Singleton @Provides @Singleton @Provides @Provides@Inject
  7. 7. Rx interface StackOverflowService { @GET("/users") fun getTopUsers(): Single<List<User>> @GET("/users/{userId}/badges") fun getBadges(@Path("userId") userId: Int): Single<List<Badge>> } Activity Presenter Interactor Retrofit Service Rx SingletonSingleton
  8. 8. Rx class UserInteractor( private val service: StackOverflowService ) { fun loadUsers(): Single<List<UserStats>> {
 //...
 } } Activity Presenter Interactor Retrofit Service Rx SingletonSingleton
  9. 9. Rx class UserListPresenter( private val userInteractor: UserInteractor, private val activity: UserListActivity ) { fun reloadUserList() { userInteractor .loadUsers() //...
 .subscribe( { activity.updateText(it) }, { activity.showError(it) } ) } } Activity Presenter Interactor Retrofit Service Rx SingletonSingleton
  10. 10. Rx class UserListActivity : AppCompatActivity() { @Inject lateinit var presenter: UserListPresenter override fun onCreate(savedInstanceState: Bundle?) { //... component.userListComponent(UserListModule(this)).inject(this) presenter.reloadUserList() } fun updateText(s: String) { //... } fun showError(t: Throwable) { //... } } Activity Presenter Interactor Retrofit Service Rx SingletonSingleton
  11. 11. @Singleton @Component(modules = arrayOf(UserInteractorModule::class, StackOverflowServiceModule::class)) interface ApplicationComponent { fun userListComponent(module: UserListModule): UserListComponent } @Module class UserInteractorModule { @Provides @Singleton fun provideUserInteractor() {
 //...
 } } @Module class StackOverflowServiceModule { @Provides @Singleton fun provideStackOverflowService() {
 //...
 }
 } @Subcomponent(modules = arrayOf(UserListModule::class)) interface UserListComponent { fun inject(activity: UserListActivity) } @Module class UserListModule { @Provides fun providePresenter() { //... } }
  12. 12. Espresso Activity Presenter Interactor Retrofit Service UnitUnitUnit Integration UI UI UI E2E JVM
  13. 13. Return Of Investment Net profit Investment
  14. 14. Testing pyramid Unit E2E Integration Manual tests
  15. 15. 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
  16. 16. Mockito tips
  17. 17. 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);
 }_
 }__
  18. 18. class MyTest { @Rule val mockitoRule = MockitoJUnit.rule() @Mock internal var collaborator1: Collaborator1? = null @Mock internal var collaborator2: Collaborator2? = null @InjectMocks internal var objectUnderTest: ObjectUnderTest? = null @Test fun myTestMethod() { //Arrange `when`(collaborator1!!.provideValue()).thenReturn(2) //Act objectUnderTest!!.execute() //Assert verify(collaborator2).printValue(10) assertThat(objectUnderTest!!.value).isEqualTo(10) }_
 }__
  19. 19. class MyTest { val collaborator1: Collaborator1 = mock() val collaborator2: Collaborator2 = mock() val objectUnderTest = ObjectUnderTest(collaborator1, collaborator2) @Test fun myTestMethod() { //Arrange whenever(collaborator1.provideValue()).thenReturn(2) //Act objectUnderTest.execute() //Assert verify(collaborator2).printValue(10) assertThat(objectUnderTest.value).isEqualTo(10) }_
 }__
  20. 20. Methods and classes are final Define classes and methods as open Always define interfaces All open compiler plugin kotlinlang.org/docs/reference/compiler-plugins.html MockMaker hadihariri.com/2016/10/04/Mocking-Kotlin-With-Mockito DexOpener for instrumentation tests github.com/tmurakami/dexopener
  21. 21. Assertion libraries Assertj joel-costigliola.github.io/assertj Kluent github.com/MarkusAmshove/Kluent Expekt github.com/winterbe/expekt Assertk github.com/willowtreeapps/assertk
  22. 22. class MyTest { val collaborator1: Collaborator1 = mock() val collaborator2: Collaborator2 = mock() val objectUnderTest = ObjectUnderTest(collaborator1, collaborator2) @Test fun myTestMethod() { //Arrange whenever(collaborator1.provideValue()).thenReturn(2) //Act objectUnderTest.execute() //Assert verify(collaborator2).printValue(10) assert }_
 }__ (objectUnderTest.value).isEqualTo(10)That
  23. 23. Testing libraries JUnit4 junit.org/junit4 JUnit5 junit.org/junit5 spek github.com/spekframework/spek Kotlin test github.com/kotlintest/kotlintest
  24. 24. class MyTest { val collaborator1: Collaborator1 = mock() val collaborator2: Collaborator2 = mock() val objectUnderTest = ObjectUnderTest(collaborator1, collaborator2) @Test fun myTestMethod() { //Arrange whenever(collaborator1.provideValue()).thenReturn(2) //Act objectUnderTest.execute() //Assert verify(collaborator2).printValue(10) assert(objectUnderTest.value).isEqualTo(10) }_
 }__
  25. 25. Activity Presenter Interactor Espresso Activity Presenter Retrofit Service
  26. 26. class UserListPresenter( private val userInteractor: UserInteractor, private val activity: UserListActivity ) { fun reloadUserList() { userInteractor .loadUsers() .map { l -> l.map { it.toString() } .reduce { s1, s2 -> "$s1nn$s2" } } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( { activity.updateText(it) }, { activity.showError(it) } ) } } Activity Presenter Interactor Retrofit Service
  27. 27. @Singleton @Component(modules = arrayOf( TestUserInteractorModule::class, StackOverflowServiceModule::class )) interface TestApplicationComponent : ApplicationComponent { fun inject(userListActivityTest: UserListActivityTest) } Activity Presenter Interactor Retrofit Service @Module class TestUserInteractorModule { @Provides @Singleton fun provideUserInteractor(): UserInteractor = mock() }
  28. 28. Activity Presenter Interactor Retrofit Service class UserListActivityTest { @get:Rule val rule = ActivityTestRule( UserListActivity::class.java, false, false) @Inject lateinit var userInteractor: UserInteractor @Before fun setUp() { val component = DaggerTestApplicationComponent.create() appFromInstrumentation.component = component component.inject(this) }____ @Test fun shouldDisplayUsers() { }_
 }__ whenever(userInteractor.loadUsers()).thenReturn( Single.just(listOf( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3")) ))) rule.launchActivity(null) onView(withId(R.id.text)).check(matches(withText( "50 user1 - badge1nn30 user2 - badge2, badge3")))
  29. 29. class UserListActivityTest { @get:Rule val rule = ActivityTestRule( UserListActivity::class.java, false, false) @Inject lateinit var userInteractor: UserInteractor @Before fun setUp() { val component = DaggerTestApplicationComponent.create() appFromInstrumentation.component = component component.inject(this) }____ @Test fun shouldDisplayUsers() { whenever(userInteractor.loadUsers()).thenReturn( Single.just(listOf( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3")) ))) rule.launchActivity(null) onView(withId(R.id.text)).check(matches(withText( "50 user1 - badge1nn30 user2 - badge2, badge3"))) }_
 }__ Activity Presenter Interactor Retrofit Service
  30. 30. Activity Presenter Interactor Retrofit Service class UserListPresenter( private val userInteractor: UserInteractor, private val activity: UserListActivity ) { fun reloadUserList() { userInteractor .loadUsers() .map { l -> l.map { it.toString() } .reduce { s1, s2 -> "$s1nn$s2" } } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( { activity.updateText(it) }, { activity.showError(it) } )___ }_
 }__
  31. 31. class AsyncTaskSchedulerRule : TestWatcher() { private val asyncTaskScheduler = Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR) override fun starting(description: Description?) { RxJavaPlugins.setIoSchedulerHandler { asyncTaskScheduler } RxJavaPlugins.setComputationSchedulerHandler { asyncTaskScheduler } RxJavaPlugins.setNewThreadSchedulerHandler { asyncTaskScheduler } } override fun finished(description: Description?) = RxJavaPlugins.reset() }
  32. 32. @get:Rule val rule = ActivityTestRule( UserListActivity::class.java, false, false) @Inject lateinit var userInteractor: UserInteractor @Before fun setUp() { val component = DaggerTestApplicationComponent.create() appFromInstrumentation.component = component component.inject(this) }A @Test fun shouldDisplayUsers() { whenever(userInteractor.loadUsers()).thenReturn( Single.just(listOf( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3")) ))) rule.launchActivity(null) onView(withId(R.id.text)).check(matches(withText( "50 user1 - badge1nn30 user2 - badge2, badge3"))) }B }C Activity Presenter Interactor Retrofit Service ArrangeActAssert @get:Rule val asyncTaskRule = AsyncTaskSchedulerRule() class UserListActivityTest {
  33. 33. class UserListActivityTest { @get:Rule val asyncTaskRule = AsyncTaskSchedulerRule() @get:Rule val rule = ActivityTestRule( UserListActivity::class.java, false, false) @Inject lateinit var userInteractor: UserInteractor @Before fun setUp() { val component = DaggerTestApplicationComponent.create() appFromInstrumentation.component = component component.inject(this) }A @Test fun shouldDisplayUsers() { whenever(userInteractor.loadUsers()).thenReturn( Single.just(listOf( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3")) ))) rule.launchActivity(null) onView(withId(R.id.text)).check(matches(withText( "50 user1 - badge1nn30 user2 - badge2, badge3"))) }B }C Activity Presenter Interactor Retrofit Service ArrangeActAssert
  34. 34. Activity Presenter Interactor Retrofit Service ArrangeActAssert class UserListActivityTest { @get:Rule val asyncTaskRule = AsyncTaskSchedulerRule() @get:Rule val rule = ActivityTestRule( UserListActivity::class.java, false, false) @Inject lateinit var userInteractor: UserInteractor @Before fun setUp() { val component = DaggerTestApplicationComponent.create() appFromInstrumentation.component = component component.inject(this) }A @Test fun shouldDisplayUsers() { whenever(userInteractor.loadUsers()).thenReturn( Single.just(listOf( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3")) ))) rule.launchActivity(null) onView(withId(R.id.text)).check(matches(withText( "50 user1 - badge1nn30 user2 - badge2, badge3"))) }B }C
  35. 35. class UserListActivityTest { @get:Rule val asyncTaskRule = AsyncTaskSchedulerRule() @get:Rule val rule = activityRule<UserListActivity>() @Inject lateinit var userInteractor: UserInteractor @Before fun setUp() { val component = DaggerTestApplicationComponent.create() appFromInstrumentation.component = component component.inject(this) }A @Test fun shouldDisplayUsers() { userInteractor.loadUsers() willReturnJust listOf( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3")) )__ rule.launchActivity() }B }C Activity Presenter Interactor Retrofit Service ArrangeActAssert R.id.text hasText "50 user1 - badge1nn30 user2 - badge2, badge3"
  36. 36. val appFromInstrumentation: MyApp get() = InstrumentationRegistry.getInstrumentation() .targetContext.applicationContext as MyApp inline fun <reified T : Activity> activityRule( initialTouchMode: Boolean = false, launchActivity: Boolean = false) = ActivityTestRule(T::class.java, initialTouchMode, launchActivity) infix fun <T> Single<T>?.willReturnJust(value: T): BDDMockito.BDDMyOngoingStubbing<Single<T>?> = given(this).willReturn(Single.just(value)) fun <T : Activity> ActivityTestRule<T>.launchActivity(): T = launchActivity(null) infix fun Int.hasText(text: String): ViewInteraction = Espresso.onView(ViewMatchers.withId(this)) .check(ViewAssertions.matches(ViewMatchers.withText(text)))
  37. 37. @Singleton @Component(modules = arrayOf( TestUserInteractorModule::class, StackOverflowServiceModule::class )) interface TestApplicationComponent : ApplicationComponent { fun inject(userListActivityTest: UserListActivityTest) } Activity Presenter Interactor Retrofit Service @Module class TestUserInteractorModule { @Provides @Singleton fun provideUserInteractor(): UserInteractor = mock() } //... @Inject lateinit var userInteractor: UserInteractor @Before fun setUp() { val component = DaggerTestApplicationComponent.create() appFromInstrumentation.component = component component.inject(this) } //...
  38. 38. Activity Presenter Interactor Retrofit Service //... val userInteractor: UserInteractor = mock() @Before fun setUp() { val component = DaggerApplicationComponent.builder() .userInteractorModule(object : UserInteractorModule() { override fun provideUserInteractor(s: StackOverflowService) = userInteractor }) .build() appFromInstrumentation.component = component } //...
  39. 39. @get:Rule val daggerMockRule = DaggerMock.rule<ApplicationComponent>( UserInteractorModule()) { set { appFromInstrumentation.component = it } }_ val userInteractor: UserInteractor = mock() Activity Presenter Interactor Retrofit Service //... //...
  40. 40. Activity Presenter Interactor Retrofit Service class UserListActivityTest { @get:Rule val asyncTaskRule = AsyncTaskSchedulerRule() @get:Rule val rule = activityRule<UserListActivity>() @Test fun shouldDisplayUsers() { userInteractor.loadUsers() willReturnJust listOf( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3")) ) rule.launchActivity() R.id.text hasText "50 user1 - badge1nn30 user2 - badge2, badge3" } } @get:Rule val daggerMockRule = DaggerMock.rule<ApplicationComponent>( UserInteractorModule()) { set { appFromInstrumentation.component = it } }_ val userInteractor: UserInteractor = mock()
  41. 41. DaggerMock: supported Dagger features Subcomponents Dependent components Dagger-Android Static methods @Binds annotation
  42. 42. Activity Presenter Interactor Retrofit Service Espresso Presenter
  43. 43. class MockPresenterTest { @get:Rule val rule = activityRule<UserListActivity>() @get:Rule val daggerMockRule = DaggerMock.rule<ApplicationComponent>( UserInteractorModule()) { set { appFromInstrumentation.component = it } }_ val presenter: UserListPresenter = mock() @Test fun testOnCreate() { rule.launchActivity(null) R.id.text hasText "" verify(presenter).reloadUserList() } } Activity Presenter Interactor Retrofit Service
  44. 44. Testing RxJava code 1.void method that uses RxJava schedulers 2.method that returns a synchronous RxJava object 3.method that returns an asynchronous RxJava object
  45. 45. Activity Interactor Retrofit Service Activity Presenter JVM
  46. 46. Activity Presenter Interactor Retrofit Service class UserListPresenter( private val userInteractor: UserInteractor, private val activity: UserListActivity ) { } fun reloadUserList() { userInteractor .loadUsers() .map { l -> l.map { it.toString() } .reduce { s1, s2 -> "$s1nn$s2" } }___ .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( { activity.updateText(it) }, { activity.showError(it) } )__ }_
  47. 47. class UserListPresenterTest { val userInteractor: UserInteractor = mock() val activity: UserListActivity = mock() val presenter = UserListPresenter(userInteractor, activity) @Test fun shouldLoadUsers() { } }_ Activity Presenter Interactor Retrofit Service userInteractor.loadUsers() willReturnJust listOf( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3")) )__ presenter.reloadUserList() verify(activity, never()).showError(any()) verify(activity).updateText( "50 user1 - badge1nn30 user2 - badge2, badge3")
  48. 48. class TrampolineSchedulerRule : TestWatcher() { override fun starting(description: Description?) { super.starting(description) RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() } RxJavaPlugins.setComputationSchedulerHandler { Schedulers.trampoline() } RxJavaPlugins.setNewThreadSchedulerHandler { Schedulers.trampoline() } RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() } } override fun finished(description: Description?) { super.finished(description) RxJavaPlugins.reset() RxAndroidPlugins.reset() } }
  49. 49. Activity Presenter Interactor Retrofit Service val userInteractor: UserInteractor = mock() val activity: UserListActivity = mock() val presenter = UserListPresenter(userInteractor, activity) @Test fun shouldLoadUsers() { userInteractor.loadUsers() willReturnJust listOf( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3")) )_ presenter.reloadUserList() verify(activity, never()).showError(any()) verify(activity).updateText( "50 user1 - badge1nn30 user2 - badge2, badge3") }__ }___ @get:Rule val schedulerRule = TrampolineSchedulerRule() class UserListPresenterTest {
  50. 50. class UserListPresenterTest { @get:Rule val schedulerRule = TrampolineSchedulerRule() val userInteractor: UserInteractor = mock() val activity: UserListActivity = mock() val presenter = UserListPresenter(userInteractor, activity) @Test fun shouldLoadUsers() { userInteractor.loadUsers() willReturnJust listOf( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3")) )_ presenter.reloadUserList() verify(activity, never()).showError(any()) verify(activity).updateText( "50 user1 - badge1nn30 user2 - badge2, badge3") }__ }___ Activity Presenter Interactor Retrofit Service
  51. 51. Testing RxJava code 1.void method that uses RxJava schedulers 2.method that returns a synchronous RxJava object 3.method that returns an asynchronous RxJava object trampoline scheduler
  52. 52. Activity Presenter Interactor Retrofit Service JVM Interactor
  53. 53. Activity Presenter Interactor Retrofit Service class UserInteractor( private val service: StackOverflowService ) { fun loadUsers(): Single<List<UserStats>> = } service.getTopUsers() .flattenAsObservable { it } .take(5) .flatMap { user -> service.getBadges(user.id) .map { badges -> UserStats(user, badges.map { it.name }) }_ .toObservable() }__ .toList()
  54. 54. class UserInteractorTest { val stackOverflowService: StackOverflowService = mock() val userInteractor = UserInteractor(stackOverflowService) @Test fun shouldLoadUsers() { stackOverflowService.getTopUsers() willReturnJust listOf( User(1, 50, "user1"), User(2, 30, "user2") )_1 stackOverflowService.getBadges(1) willReturnJust listOf( Badge("badge1") )_2 stackOverflowService.getBadges(2) willReturnJust listOf( Badge("badge2"), Badge("badge3") )_3 val users = userInteractor.loadUsers().blockingGet() assert(users).containsExactly( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3"))) }__ }_ Activity Presenter Interactor Retrofit Service
  55. 55. Testing RxJava code 1.void method that uses RxJava schedulers 2.method that returns a synchronous RxJava object 3.method that returns an asynchronous RxJava object trampoline scheduler blockingGet
  56. 56. .map { badges -> UserStats(user, badges.map { it.name }) }__ .toObservable() }_ .toList() .subscribeOn(Schedulers.io()) Activity Presenter Interactor Retrofit Service fun loadUsers(): Single<List<UserStats>> = service.getTopUsers() .flattenAsObservable { it } .take(5) .flatMap { user -> service.getBadges(user.id)
  57. 57. Activity Presenter Interactor Retrofit Service class UserInteractorTest { val stackOverflowService: StackOverflowService = mock() val userInteractor = UserInteractor(stackOverflowService) @Test fun shouldLoadUsers() { stackOverflowService.getTopUsers() willReturnJust listOf( User(1, 50, "user1"), User(2, 30, "user2") )_1 stackOverflowService.getBadges(1) willReturnJust listOf( Badge("badge1") )_2 stackOverflowService.getBadges(2) willReturnJust listOf( Badge("badge2"), Badge("badge3") )_3 val users = userInteractor.loadUsers().blockingGet() assert(users).containsExactly( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3"))) }__ }_
  58. 58. class UserInteractorTest { val stackOverflowService: StackOverflowService = mock() val userInteractor = UserInteractor(stackOverflowService) @Test fun shouldLoadUsers() { stackOverflowService.getTopUsers() willReturnJust listOf( User(1, 50, "user1"), User(2, 30, "user2") )_1 stackOverflowService.getBadges(1) willReturnJust listOf( Badge("badge1") )_2 stackOverflowService.getBadges(2) willReturnJust listOf( Badge("badge2"), Badge("badge3") )_3 val users = userInteractor.loadUsers().blockingGet() assert(users).containsExactly( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3"))) }__ }_ Activity Presenter Interactor Retrofit Service
  59. 59. Activity Presenter Interactor Retrofit Service stackOverflowService.getTopUsers() willReturnJust listOf( User(1, 50, "user1"), User(2, 30, "user2") )_1 stackOverflowService.getBadges(1) willReturnJust listOf( Badge("badge1") )_2 stackOverflowService.getBadges(2) willReturnJust listOf( Badge("badge2"), Badge("badge3") )_3 val users = userInteractor.loadUsers().blockingGet() assert(users).containsExactly( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3")))
  60. 60. val testObserver = userInteractor.loadUsers().test() val users = testObserver.assertNoErrors().values()[0] Activity Presenter Interactor Retrofit Service stackOverflowService.getTopUsers() willReturnJust listOf( User(1, 50, "user1"), User(2, 30, "user2") )_1 stackOverflowService.getBadges(1) willReturnJust listOf( Badge("badge1") )_2 stackOverflowService.getBadges(2) willReturnJust listOf( Badge("badge2"), Badge("badge3") )_3 assert(users).containsExactly( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3")))
  61. 61. Activity Presenter Interactor Retrofit Service stackOverflowService.getTopUsers() willReturnJust listOf( User(1, 50, "user1"), User(2, 30, "user2") )_1 stackOverflowService.getBadges(1) willReturn just(listOf(Badge("badge1"))) .delay(2, SECONDS) stackOverflowService.getBadges(2) willReturn just(listOf(Badge("badge2"), Badge("badge3"))) .delay(1, SECONDS) val testObserver = userInteractor.loadUsers().test() val users = testObserver.assertNoErrors().values()[0] assert(users).containsExactly( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3")))
  62. 62. class TestSchedulerRule : TestWatcher() { val testScheduler = TestScheduler() override fun starting(description: Description?) { RxJavaPlugins.setIoSchedulerHandler { testScheduler } RxJavaPlugins.setComputationSchedulerHandler { testScheduler } RxJavaPlugins.setNewThreadSchedulerHandler { testScheduler } RxAndroidPlugins.setMainThreadSchedulerHandler { Schedulers.trampoline() } } override fun finished(description: Description?) { RxJavaPlugins.reset() RxAndroidPlugins.reset() } }
  63. 63. val users = testObserver.assertNoErrors().values()[0] assert(users).containsExactly( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3"))) schedulerRule.testScheduler .advanceTimeBy(2, TimeUnit.SECONDS) Activity Presenter Interactor Retrofit Service stackOverflowService.getTopUsers() willReturnJust listOf( User(1, 50, "user1"), User(2, 30, "user2") )_1 stackOverflowService.getBadges(1) willReturn just(listOf(Badge("badge1"))) .delay(2, SECONDS) stackOverflowService.getBadges(2) willReturn just(listOf(Badge("badge2"), Badge("badge3"))) .delay(1, SECONDS) val testObserver = userInteractor.loadUsers().test()
  64. 64. stackOverflowService.getTopUsers() willReturnJust listOf( User(1, 50, "user1"), User(2, 30, "user2") )_1 stackOverflowService.getBadges(1) willReturn just(listOf(Badge("badge1"))) .delay(2, SECONDS) stackOverflowService.getBadges(2) willReturn just(listOf(Badge("badge2"), Badge("badge3"))) .delay(1, SECONDS) val testObserver = userInteractor.loadUsers().test() schedulerRule.testScheduler .advanceTimeBy(2, TimeUnit.SECONDS) val users = testObserver.assertNoErrors().values()[0] assert(users).containsExactly( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3"))) Activity Presenter Interactor Retrofit Service
  65. 65. Activity Presenter Interactor Retrofit Service class UserInteractor( private val service: StackOverflowService ) { fun loadUsers(): Single<List<UserStats>> = service.getTopUsers() .flattenAsObservable { it } .take(5) .flatMap { user -> service.getBadges(user.id) .subscribeOn(Schedulers.io()) .map { badges -> UserStats(user, badges.map { it.name }) }___ .toObservable() }__ .toList() }_
  66. 66. class UserInteractor( private val service: StackOverflowService ) { fun loadUsers(): Single<List<UserStats>> = service.getTopUsers() .flattenAsObservable { it } .take(5) .flatMap { user -> service.getBadges(user.id) .subscribeOn(Schedulers.io()) .map { badges -> UserStats(user, badges.map { it.name }) }___ .toObservable() }__ .toList() }_ Activity Presenter Interactor Retrofit Service
  67. 67. class UserInteractor( private val service: StackOverflowService ) { fun loadUsers(): Single<List<UserStats>> = service.getTopUsers() .flattenAsObservable { it } .take(5) .flatMap { user -> service.getBadges(user.id) .subscribeOn(Schedulers.io()) .map { badges -> UserStats(user, badges.map { it.name }) }___ .toObservable() }__ .toList() }_ Activity Presenter Interactor Retrofit Service
  68. 68. class UserInteractor( private val service: StackOverflowService ) { fun loadUsers(): Single<List<UserStats>> = service.getTopUsers() .flattenAsObservable { it } .take(5) . service.getBadges(user.id) .subscribeOn(Schedulers.io()) .map { badges -> UserStats(user, badges.map { it.name }) }___ .toObservable() }__ .toList() }_ concatMapEager Activity Presenter Interactor Retrofit Service { user ->
  69. 69. Activity Presenter Interactor Retrofit Service stackOverflowService.getTopUsers() willReturnJust listOf( User(1, 50, "user1"), User(2, 30, "user2") )_1 stackOverflowService.getBadges(1) willReturn just(listOf(Badge("badge1"))) .delay(2, SECONDS) stackOverflowService.getBadges(2) willReturn just(listOf(Badge("badge2"), Badge("badge3"))) .delay(1, SECONDS) val testObserver = userInteractor.loadUsers().test() schedulerRule.testScheduler .advanceTimeBy(2, TimeUnit.SECONDS) val users = testObserver.assertNoErrors().values()[0] assert(users).containsExactly( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3")))
  70. 70. stackOverflowService.getTopUsers() willReturnJust listOf( User(1, 50, "user1"), User(2, 30, "user2") )_1 stackOverflowService.getBadges(1) willReturn just(listOf(Badge("badge1"))) .delay(2, SECONDS) stackOverflowService.getBadges(2) willReturn just(listOf(Badge("badge2"), Badge("badge3"))) .delay(1, SECONDS) val testObserver = userInteractor.loadUsers().test() schedulerRule.testScheduler .advanceTimeBy(2, TimeUnit.SECONDS) val users = testObserver.assertNoErrors().values()[0] assert(users).containsExactly( UserStats(1, 50, "user1", listOf("badge1")), UserStats(2, 30, "user2", listOf("badge2", "badge3"))) Activity Presenter Interactor Retrofit Service
  71. 71. Testing RxJava code 1.void method that uses RxJava schedulers 2.method that returns a synchronous RxJava object 3.method that returns an asynchronous RxJava object trampoline scheduler blockingGet TestScheduler & TestObserver
  72. 72. Wrapping up 1.Using DaggerMock testing boilerplate code can be reduced 2.RxJava asynchronous code can be tested using TestObserver and TestScheduler 3.Test code can be simplified using Kotlin extension functions
  73. 73. Links github.com/fabioCollini/TestingDaggerRxJava github.com/fabioCollini/DaggerMock github.com/nhaarman/mockito-kotlin 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 artemzin.com/blog/jfyi-overriding-module-classes-with-dagger2/
  74. 74. Thanks for your attention! Questions?

×