SlideShare a Scribd company logo
1 of 81
Download to read offline
Testing Android apps based on
Dagger and RxJava
Fabio Collini
droidcon London October 2017
Ego slide
@fabioCollini
linkedin.com/in/fabiocollini
github.com/fabioCollini
medium.com/@fabioCollini
codingjam.it
Android programmazione avanzata
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
github.com/fabioCollini/TestingDaggerRxJava
Activity
Presenter
Interactor
Retrofit
Service
Rx
Activity
Presenter
Interactor
Retrofit
Service
Rx
@Singleton
@Provides
@Singleton
@Provides
@Provides@Inject
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
Rx
class UserInteractor(
private val service: StackOverflowService
) {
fun loadUsers(): Single<List<UserStats>> {

//...

}
}
Activity
Presenter
Interactor
Retrofit
Service
Rx
SingletonSingleton
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
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
@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() {
//...
}
}
Espresso
Activity
Presenter
Interactor
Retrofit
Service
UnitUnitUnit
Integration
UI
UI
UI
E2E
JVM
Return Of Investment
Net profit
Investment
Testing pyramid
Unit
E2E
Integration
Manual
tests
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
Mockito tips
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);

}_

}__
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)
}_

}__
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)
}_

}__
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
Assertion libraries
Assertj
joel-costigliola.github.io/assertj
Kluent
github.com/MarkusAmshove/Kluent
Expekt
github.com/winterbe/expekt
Assertk
github.com/willowtreeapps/assertk
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
Testing libraries
JUnit4
junit.org/junit4
JUnit5
junit.org/junit5
spek
github.com/spekframework/spek
Kotlin test
github.com/kotlintest/kotlintest
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)
}_

}__
Activity
Presenter
Interactor
Espresso
Activity
Presenter
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) }
)
}
}
Activity
Presenter
Interactor
Retrofit
Service
@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()
}
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")))
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
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) }
)___
}_

}__
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()
}
@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 {
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
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
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"
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)))
@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)
}
//...
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
}
//...
@get:Rule
val daggerMockRule = DaggerMock.rule<ApplicationComponent>(
UserInteractorModule()) {
set { appFromInstrumentation.component = it }
}_
val userInteractor: UserInteractor = mock()
Activity
Presenter
Interactor
Retrofit
Service
//...
//...
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()
DaggerMock: supported Dagger features
Subcomponents
Dependent components
Dagger-Android
Static methods
@Binds annotation
Activity
Presenter
Interactor
Retrofit
Service
Espresso
Presenter
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
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
Activity
Interactor
Retrofit
Service
Activity
Presenter
JVM
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) }
)__
}_
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")
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()
}
}
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 {
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
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
Activity
Presenter
Interactor
Retrofit
Service
JVM
Interactor
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()
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
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
.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)
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")))
}__
}_
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
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")))
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")))
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")))
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()
}
}
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()
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
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()
}_
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
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
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 ->
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")))
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
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
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
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/
Thanks for your attention!
Questions?

More Related Content

What's hot

Solid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon ItalySolid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon ItalyFabio Collini
 
Advanced Dagger talk from 360andev
Advanced Dagger talk from 360andevAdvanced Dagger talk from 360andev
Advanced Dagger talk from 360andevMike Nakhimovich
 
Android programming -_pushing_the_limits
Android programming -_pushing_the_limitsAndroid programming -_pushing_the_limits
Android programming -_pushing_the_limitsDroidcon Berlin
 
Android dev toolbox
Android dev toolboxAndroid dev toolbox
Android dev toolboxShem Magnezi
 
Automated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.xAutomated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.xTatsuya Maki
 
Introduction to Retrofit and RxJava
Introduction to Retrofit and RxJavaIntroduction to Retrofit and RxJava
Introduction to Retrofit and RxJavaFabio Collini
 
Taming Core Data by Arek Holko, Macoscope
Taming Core Data by Arek Holko, MacoscopeTaming Core Data by Arek Holko, Macoscope
Taming Core Data by Arek Holko, MacoscopeMacoscope
 
Dagger 2 - Injeção de Dependência
Dagger 2 - Injeção de DependênciaDagger 2 - Injeção de Dependência
Dagger 2 - Injeção de DependênciaEdson Menegatti
 
GKAC 2015 Apr. - RxAndroid
GKAC 2015 Apr. - RxAndroidGKAC 2015 Apr. - RxAndroid
GKAC 2015 Apr. - RxAndroidGDG Korea
 
Android architecture component - FbCircleDev Yogyakarta Indonesia
Android architecture component - FbCircleDev Yogyakarta IndonesiaAndroid architecture component - FbCircleDev Yogyakarta Indonesia
Android architecture component - FbCircleDev Yogyakarta IndonesiaPratama Nur Wijaya
 
Reactive, component 그리고 angular2
Reactive, component 그리고  angular2Reactive, component 그리고  angular2
Reactive, component 그리고 angular2Jeado Ko
 
Daggerate your code - Write your own annotation processor
Daggerate your code - Write your own annotation processorDaggerate your code - Write your own annotation processor
Daggerate your code - Write your own annotation processorBartosz Kosarzycki
 
Android basic 4 Navigation Drawer
Android basic 4 Navigation DrawerAndroid basic 4 Navigation Drawer
Android basic 4 Navigation DrawerEakapong Kattiya
 
Under the Hood: Using Spring in Grails
Under the Hood: Using Spring in GrailsUnder the Hood: Using Spring in Grails
Under the Hood: Using Spring in GrailsBurt Beckwith
 
JEEConf 2017 - The hitchhiker’s guide to Java class reloading
JEEConf 2017 - The hitchhiker’s guide to Java class reloadingJEEConf 2017 - The hitchhiker’s guide to Java class reloading
JEEConf 2017 - The hitchhiker’s guide to Java class reloadingAnton Arhipov
 

What's hot (20)

Solid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon ItalySolid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon Italy
 
Advanced Dagger talk from 360andev
Advanced Dagger talk from 360andevAdvanced Dagger talk from 360andev
Advanced Dagger talk from 360andev
 
Android programming -_pushing_the_limits
Android programming -_pushing_the_limitsAndroid programming -_pushing_the_limits
Android programming -_pushing_the_limits
 
Android dev toolbox
Android dev toolboxAndroid dev toolbox
Android dev toolbox
 
Automated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.xAutomated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.x
 
Introduction to Retrofit and RxJava
Introduction to Retrofit and RxJavaIntroduction to Retrofit and RxJava
Introduction to Retrofit and RxJava
 
Taming Core Data by Arek Holko, Macoscope
Taming Core Data by Arek Holko, MacoscopeTaming Core Data by Arek Holko, Macoscope
Taming Core Data by Arek Holko, Macoscope
 
Dagger 2 - Injeção de Dependência
Dagger 2 - Injeção de DependênciaDagger 2 - Injeção de Dependência
Dagger 2 - Injeção de Dependência
 
GKAC 2015 Apr. - RxAndroid
GKAC 2015 Apr. - RxAndroidGKAC 2015 Apr. - RxAndroid
GKAC 2015 Apr. - RxAndroid
 
Java Quiz - Meetup
Java Quiz - MeetupJava Quiz - Meetup
Java Quiz - Meetup
 
Open sourcing the store
Open sourcing the storeOpen sourcing the store
Open sourcing the store
 
Android architecture component - FbCircleDev Yogyakarta Indonesia
Android architecture component - FbCircleDev Yogyakarta IndonesiaAndroid architecture component - FbCircleDev Yogyakarta Indonesia
Android architecture component - FbCircleDev Yogyakarta Indonesia
 
Reactive, component 그리고 angular2
Reactive, component 그리고  angular2Reactive, component 그리고  angular2
Reactive, component 그리고 angular2
 
Android basic 3 Dialogs
Android basic 3 DialogsAndroid basic 3 Dialogs
Android basic 3 Dialogs
 
Android basic 2 UI Design
Android basic 2 UI DesignAndroid basic 2 UI Design
Android basic 2 UI Design
 
Daggerate your code - Write your own annotation processor
Daggerate your code - Write your own annotation processorDaggerate your code - Write your own annotation processor
Daggerate your code - Write your own annotation processor
 
Android basic 4 Navigation Drawer
Android basic 4 Navigation DrawerAndroid basic 4 Navigation Drawer
Android basic 4 Navigation Drawer
 
Junit 5 - Maior e melhor
Junit 5 - Maior e melhorJunit 5 - Maior e melhor
Junit 5 - Maior e melhor
 
Under the Hood: Using Spring in Grails
Under the Hood: Using Spring in GrailsUnder the Hood: Using Spring in Grails
Under the Hood: Using Spring in Grails
 
JEEConf 2017 - The hitchhiker’s guide to Java class reloading
JEEConf 2017 - The hitchhiker’s guide to Java class reloadingJEEConf 2017 - The hitchhiker’s guide to Java class reloading
JEEConf 2017 - The hitchhiker’s guide to Java class reloading
 

Similar to Testing Android apps based on Dagger and RxJava Droidcon UK

Sword fighting with Dagger GDG-NYC Jan 2016
 Sword fighting with Dagger GDG-NYC Jan 2016 Sword fighting with Dagger GDG-NYC Jan 2016
Sword fighting with Dagger GDG-NYC Jan 2016Mike Nakhimovich
 
How to become an Android dev starting from iOS (and vice versa)
How to become an Android dev starting from iOS (and vice versa)How to become an Android dev starting from iOS (and vice versa)
How to become an Android dev starting from iOS (and vice versa)Giuseppe Filograno
 
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STM
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STMConcurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STM
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STMMario Fusco
 
2. Design patterns. part #2
2. Design patterns. part #22. Design patterns. part #2
2. Design patterns. part #2Leonid Maslov
 
Workshop 26: React Native - The Native Side
Workshop 26: React Native - The Native SideWorkshop 26: React Native - The Native Side
Workshop 26: React Native - The Native SideVisual Engineering
 
JSAnkara Swift v React Native
JSAnkara Swift v React NativeJSAnkara Swift v React Native
JSAnkara Swift v React NativeMuhammed Demirci
 
Building Modern Apps using Android Architecture Components
Building Modern Apps using Android Architecture ComponentsBuilding Modern Apps using Android Architecture Components
Building Modern Apps using Android Architecture ComponentsHassan Abid
 
Durable functions 2.0 (2019-10-10)
Durable functions 2.0 (2019-10-10)Durable functions 2.0 (2019-10-10)
Durable functions 2.0 (2019-10-10)Paco de la Cruz
 
React & The Art of Managing Complexity
React &  The Art of Managing ComplexityReact &  The Art of Managing Complexity
React & The Art of Managing ComplexityRyan Anklam
 
Dependency Injection for Android @ Ciklum speakers corner Kiev 29. May 2014
Dependency Injection for Android @ Ciklum speakers corner Kiev 29. May 2014Dependency Injection for Android @ Ciklum speakers corner Kiev 29. May 2014
Dependency Injection for Android @ Ciklum speakers corner Kiev 29. May 2014First Tuesday Bergen
 
K is for Kotlin
K is for KotlinK is for Kotlin
K is for KotlinTechMagic
 
Kotlin Perfomance on Android / Александр Смирнов (Splyt)
Kotlin Perfomance on Android / Александр Смирнов (Splyt)Kotlin Perfomance on Android / Александр Смирнов (Splyt)
Kotlin Perfomance on Android / Александр Смирнов (Splyt)Ontico
 
Cloud native programming model comparison
Cloud native programming model comparisonCloud native programming model comparison
Cloud native programming model comparisonEmily Jiang
 
Sharper Better Faster Dagger ‡ - Droidcon SF
Sharper Better Faster Dagger ‡ - Droidcon SFSharper Better Faster Dagger ‡ - Droidcon SF
Sharper Better Faster Dagger ‡ - Droidcon SFPierre-Yves Ricau
 
Riga DevDays 2017 - The hitchhiker’s guide to Java class reloading
Riga DevDays 2017 - The hitchhiker’s guide to Java class reloadingRiga DevDays 2017 - The hitchhiker’s guide to Java class reloading
Riga DevDays 2017 - The hitchhiker’s guide to Java class reloadingAnton Arhipov
 
Martin Anderson - threads v actors
Martin Anderson - threads v actorsMartin Anderson - threads v actors
Martin Anderson - threads v actorsbloodredsun
 

Similar to Testing Android apps based on Dagger and RxJava Droidcon UK (20)

Sword fighting with Dagger GDG-NYC Jan 2016
 Sword fighting with Dagger GDG-NYC Jan 2016 Sword fighting with Dagger GDG-NYC Jan 2016
Sword fighting with Dagger GDG-NYC Jan 2016
 
Ngrx meta reducers
Ngrx meta reducersNgrx meta reducers
Ngrx meta reducers
 
How to become an Android dev starting from iOS (and vice versa)
How to become an Android dev starting from iOS (and vice versa)How to become an Android dev starting from iOS (and vice versa)
How to become an Android dev starting from iOS (and vice versa)
 
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STM
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STMConcurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STM
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STM
 
2. Design patterns. part #2
2. Design patterns. part #22. Design patterns. part #2
2. Design patterns. part #2
 
Workshop 26: React Native - The Native Side
Workshop 26: React Native - The Native SideWorkshop 26: React Native - The Native Side
Workshop 26: React Native - The Native Side
 
JSAnkara Swift v React Native
JSAnkara Swift v React NativeJSAnkara Swift v React Native
JSAnkara Swift v React Native
 
Building Modern Apps using Android Architecture Components
Building Modern Apps using Android Architecture ComponentsBuilding Modern Apps using Android Architecture Components
Building Modern Apps using Android Architecture Components
 
Durable functions 2.0 (2019-10-10)
Durable functions 2.0 (2019-10-10)Durable functions 2.0 (2019-10-10)
Durable functions 2.0 (2019-10-10)
 
Android best practices
Android best practicesAndroid best practices
Android best practices
 
React & The Art of Managing Complexity
React &  The Art of Managing ComplexityReact &  The Art of Managing Complexity
React & The Art of Managing Complexity
 
Dependency Injection for Android
Dependency Injection for AndroidDependency Injection for Android
Dependency Injection for Android
 
Dependency Injection for Android @ Ciklum speakers corner Kiev 29. May 2014
Dependency Injection for Android @ Ciklum speakers corner Kiev 29. May 2014Dependency Injection for Android @ Ciklum speakers corner Kiev 29. May 2014
Dependency Injection for Android @ Ciklum speakers corner Kiev 29. May 2014
 
Android 3
Android 3Android 3
Android 3
 
K is for Kotlin
K is for KotlinK is for Kotlin
K is for Kotlin
 
Kotlin Perfomance on Android / Александр Смирнов (Splyt)
Kotlin Perfomance on Android / Александр Смирнов (Splyt)Kotlin Perfomance on Android / Александр Смирнов (Splyt)
Kotlin Perfomance on Android / Александр Смирнов (Splyt)
 
Cloud native programming model comparison
Cloud native programming model comparisonCloud native programming model comparison
Cloud native programming model comparison
 
Sharper Better Faster Dagger ‡ - Droidcon SF
Sharper Better Faster Dagger ‡ - Droidcon SFSharper Better Faster Dagger ‡ - Droidcon SF
Sharper Better Faster Dagger ‡ - Droidcon SF
 
Riga DevDays 2017 - The hitchhiker’s guide to Java class reloading
Riga DevDays 2017 - The hitchhiker’s guide to Java class reloadingRiga DevDays 2017 - The hitchhiker’s guide to Java class reloading
Riga DevDays 2017 - The hitchhiker’s guide to Java class reloading
 
Martin Anderson - threads v actors
Martin Anderson - threads v actorsMartin Anderson - threads v actors
Martin Anderson - threads v actors
 

More from Fabio Collini

Using Dagger in a Clean Architecture project
Using Dagger in a Clean Architecture projectUsing Dagger in a Clean Architecture project
Using Dagger in a Clean Architecture projectFabio Collini
 
SOLID principles in practice: the Clean Architecture - Devfest Emila Romagna
SOLID principles in practice: the Clean Architecture - Devfest Emila RomagnaSOLID principles in practice: the Clean Architecture - Devfest Emila Romagna
SOLID principles in practice: the Clean Architecture - Devfest Emila RomagnaFabio Collini
 
SOLID principles in practice: the Clean Architecture
SOLID principles in practice: the Clean ArchitectureSOLID principles in practice: the Clean Architecture
SOLID principles in practice: the Clean ArchitectureFabio Collini
 
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf MilanFrom Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf MilanFabio Collini
 
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night TurinAsync code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night TurinFabio Collini
 
Recap Google I/O 2018
Recap Google I/O 2018Recap Google I/O 2018
Recap Google I/O 2018Fabio Collini
 
From java to kotlin beyond alt+shift+cmd+k - Droidcon italy
From java to kotlin beyond alt+shift+cmd+k - Droidcon italyFrom java to kotlin beyond alt+shift+cmd+k - Droidcon italy
From java to kotlin beyond alt+shift+cmd+k - Droidcon italyFabio Collini
 
From java to kotlin beyond alt+shift+cmd+k
From java to kotlin beyond alt+shift+cmd+kFrom java to kotlin beyond alt+shift+cmd+k
From java to kotlin beyond alt+shift+cmd+kFabio Collini
 
Android Data Binding in action using MVVM pattern - droidconUK
Android Data Binding in action using MVVM pattern - droidconUKAndroid Data Binding in action using MVVM pattern - droidconUK
Android Data Binding in action using MVVM pattern - droidconUKFabio Collini
 
Data Binding in Action using MVVM pattern
Data Binding in Action using MVVM patternData Binding in Action using MVVM pattern
Data Binding in Action using MVVM patternFabio Collini
 
Android Wear CodeLab - GDG Firenze
Android Wear CodeLab - GDG FirenzeAndroid Wear CodeLab - GDG Firenze
Android Wear CodeLab - GDG FirenzeFabio Collini
 
Testable Android Apps using data binding and MVVM
Testable Android Apps using data binding and MVVMTestable Android Apps using data binding and MVVM
Testable Android Apps using data binding and MVVMFabio Collini
 
Testable Android Apps DroidCon Italy 2015
Testable Android Apps DroidCon Italy 2015Testable Android Apps DroidCon Italy 2015
Testable Android Apps DroidCon Italy 2015Fabio Collini
 
Clean android code - Droidcon Italiy 2014
Clean android code - Droidcon Italiy 2014Clean android code - Droidcon Italiy 2014
Clean android code - Droidcon Italiy 2014Fabio Collini
 
Librerie su Android: come non reinventare la ruota @ whymca 2012
Librerie su Android: come non reinventare la ruota @ whymca 2012 Librerie su Android: come non reinventare la ruota @ whymca 2012
Librerie su Android: come non reinventare la ruota @ whymca 2012 Fabio Collini
 
Android Widget @ whymca 2011
Android Widget @ whymca 2011Android Widget @ whymca 2011
Android Widget @ whymca 2011Fabio Collini
 

More from Fabio Collini (16)

Using Dagger in a Clean Architecture project
Using Dagger in a Clean Architecture projectUsing Dagger in a Clean Architecture project
Using Dagger in a Clean Architecture project
 
SOLID principles in practice: the Clean Architecture - Devfest Emila Romagna
SOLID principles in practice: the Clean Architecture - Devfest Emila RomagnaSOLID principles in practice: the Clean Architecture - Devfest Emila Romagna
SOLID principles in practice: the Clean Architecture - Devfest Emila Romagna
 
SOLID principles in practice: the Clean Architecture
SOLID principles in practice: the Clean ArchitectureSOLID principles in practice: the Clean Architecture
SOLID principles in practice: the Clean Architecture
 
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf MilanFrom Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
 
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night TurinAsync code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
 
Recap Google I/O 2018
Recap Google I/O 2018Recap Google I/O 2018
Recap Google I/O 2018
 
From java to kotlin beyond alt+shift+cmd+k - Droidcon italy
From java to kotlin beyond alt+shift+cmd+k - Droidcon italyFrom java to kotlin beyond alt+shift+cmd+k - Droidcon italy
From java to kotlin beyond alt+shift+cmd+k - Droidcon italy
 
From java to kotlin beyond alt+shift+cmd+k
From java to kotlin beyond alt+shift+cmd+kFrom java to kotlin beyond alt+shift+cmd+k
From java to kotlin beyond alt+shift+cmd+k
 
Android Data Binding in action using MVVM pattern - droidconUK
Android Data Binding in action using MVVM pattern - droidconUKAndroid Data Binding in action using MVVM pattern - droidconUK
Android Data Binding in action using MVVM pattern - droidconUK
 
Data Binding in Action using MVVM pattern
Data Binding in Action using MVVM patternData Binding in Action using MVVM pattern
Data Binding in Action using MVVM pattern
 
Android Wear CodeLab - GDG Firenze
Android Wear CodeLab - GDG FirenzeAndroid Wear CodeLab - GDG Firenze
Android Wear CodeLab - GDG Firenze
 
Testable Android Apps using data binding and MVVM
Testable Android Apps using data binding and MVVMTestable Android Apps using data binding and MVVM
Testable Android Apps using data binding and MVVM
 
Testable Android Apps DroidCon Italy 2015
Testable Android Apps DroidCon Italy 2015Testable Android Apps DroidCon Italy 2015
Testable Android Apps DroidCon Italy 2015
 
Clean android code - Droidcon Italiy 2014
Clean android code - Droidcon Italiy 2014Clean android code - Droidcon Italiy 2014
Clean android code - Droidcon Italiy 2014
 
Librerie su Android: come non reinventare la ruota @ whymca 2012
Librerie su Android: come non reinventare la ruota @ whymca 2012 Librerie su Android: come non reinventare la ruota @ whymca 2012
Librerie su Android: come non reinventare la ruota @ whymca 2012
 
Android Widget @ whymca 2011
Android Widget @ whymca 2011Android Widget @ whymca 2011
Android Widget @ whymca 2011
 

Recently uploaded

Exploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdfExploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdfproinshot.com
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfonteinmasabamasaba
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Modelsaagamshah0812
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...harshavardhanraghave
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️Delhi Call girls
 
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfVishalKumarJha10
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...masabamasaba
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisamasabamasaba
 
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park %in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park masabamasaba
 
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...masabamasaba
 
%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrandmasabamasaba
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...panagenda
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providermohitmore19
 
%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburg
%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburg%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburg
%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburgmasabamasaba
 
%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...masabamasaba
 
Announcing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK SoftwareAnnouncing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK SoftwareJim McKeeth
 
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park %in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park masabamasaba
 
Generic or specific? Making sensible software design decisions
Generic or specific? Making sensible software design decisionsGeneric or specific? Making sensible software design decisions
Generic or specific? Making sensible software design decisionsBert Jan Schrijver
 
Define the academic and professional writing..pdf
Define the academic and professional writing..pdfDefine the academic and professional writing..pdf
Define the academic and professional writing..pdfPearlKirahMaeRagusta1
 
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...Shane Coughlan
 

Recently uploaded (20)

Exploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdfExploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdf
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Models
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
 
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park %in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
 
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
 
%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
 
%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburg
%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburg%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburg
%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburg
 
%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...
 
Announcing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK SoftwareAnnouncing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK Software
 
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park %in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
 
Generic or specific? Making sensible software design decisions
Generic or specific? Making sensible software design decisionsGeneric or specific? Making sensible software design decisions
Generic or specific? Making sensible software design decisions
 
Define the academic and professional writing..pdf
Define the academic and professional writing..pdfDefine the academic and professional writing..pdf
Define the academic and professional writing..pdf
 
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
 

Testing Android apps based on Dagger and RxJava Droidcon UK

  • 1. Testing Android apps based on Dagger and RxJava Fabio Collini droidcon London October 2017
  • 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
  • 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. 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() { //... } }
  • 13. Return Of Investment Net profit Investment
  • 14.
  • 15.
  • 16.
  • 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
  • 30. @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() }
  • 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) } //...
  • 41. 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 } //...
  • 42.
  • 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()
  • 45. DaggerMock: supported Dagger features Subcomponents Dependent components Dagger-Android Static methods @Binds annotation
  • 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")
  • 52.
  • 53.
  • 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
  • 59. 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()
  • 60.
  • 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
  • 72. 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() }_
  • 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
  • 81. Thanks for your attention! Questions?