More Related Content Similar to Testing Android apps based on Dagger and RxJava Droidcon UK (20) More from Fabio Collini (16) Testing Android apps based on Dagger and RxJava Droidcon UK3. 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
8. Rx
class UserInteractor(
private val service: StackOverflowService
) {
fun loadUsers(): Single<List<UserStats>> {
//...
}
}
Activity
Presenter
Interactor
Retrofit
Service
Rx
SingletonSingleton
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. 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. @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() {
//...
}
}
18. 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
20. 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);
}_
}__
21. 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)
}_
}__
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)
assertThat(objectUnderTest.value).isEqualTo(10)
}_
}__
23. 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
25. 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
27. 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)
}_
}__
29. 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
31. 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")))
32. 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
33. 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) }
)___
}_
}__
34. 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()
}
35. @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 {
36. 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
37. 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
38. 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"
39. 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)))
40. @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)
}
//...
43. @get:Rule
val daggerMockRule = DaggerMock.rule<ApplicationComponent>(
UserInteractorModule()) {
set { appFromInstrumentation.component = it }
}_
val userInteractor: UserInteractor = mock()
Activity
Presenter
Interactor
Retrofit
Service
//...
//...
44. 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()
47. 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
48. 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
50. 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) }
)__
}_
51. 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")
54. 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()
}
}
55. 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 {
56. 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
57. 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
61. 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
62. 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
63. .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)
64. 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")))
}__
}_
65. 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
66. 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")))
67. 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")))
68. 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")))
69. 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()
}
}
70. 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()
71. 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
73. 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
74. 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
75. 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 ->
76. 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")))
77. 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
78. 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
79. 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