Slide 1

Slide 1 text

TDD in Android Rivu Chakraborty https://www.rivu.dev/ AndroidWorldwide

Slide 2

Slide 2 text

Rivu Chakraborty @rivuchakraborty https://www.rivu.dev Android @

Slide 3

Slide 3 text

Types of Tests https://www.rivu.dev/ https://www.rivu.dev/

Slide 4

Slide 4 text

Test Pyramid Unit Tests Integration Tests E2E Tests

Slide 5

Slide 5 text

Unit Tests @Test fun `test getUsers`() = runBlocking { val expected = getDummyUsers() val result = testUserRepository.getUsers() assertEquals(expected, result) }

Slide 6

Slide 6 text

Integration Tests @Test fun `test getUsers`() = runBlocking { val expected = localDS.getUsers() //Actual Local DS val result = testUserRepository.getUsers().first() assertEquals(expected, result) }

Slide 7

Slide 7 text

What is TDD???? https://www.rivu.dev/ https://www.rivu.dev/

Slide 8

Slide 8 text

Test Driven Development ● Write Tests, let them fail and then write Implementation https://www.rivu.dev/

Slide 9

Slide 9 text

Test Driven Development ● Write Tests, let them fail and then write Implementation ● We want Tests to fail first https://www.rivu.dev/

Slide 10

Slide 10 text

Test Driven Development ● Write Tests, let them fail and then write Implementation ● We want Tests to fail first ● Steps ○ Write the Interface https://www.rivu.dev/

Slide 11

Slide 11 text

Test Driven Development ● Write Tests, let them fail and then write Implementation ● We want Tests to fail first ● Steps ○ Write the Interface ○ Design the Flow https://www.rivu.dev/

Slide 12

Slide 12 text

Test Driven Development ● Write Tests, let them fail and then write Implementation ● We want Tests to fail first ● Steps ○ Write the Interface ○ Design the Flow ○ Implement the methods with TODO (We still want them to compile 😉) https://www.rivu.dev/

Slide 13

Slide 13 text

Test Driven Development ● Write Tests, let them fail and then write Implementation ● We want Tests to fail first ● Steps ○ Write the Interface ○ Design the Flow ○ Implement the methods with TODO (We still want them to compile 😉) ○ Write the Tests and let them fail https://www.rivu.dev/

Slide 14

Slide 14 text

Test Driven Development ● Write Tests, let them fail and then write Implementation ● We want Tests to fail first ● Steps ○ Write the Interface ○ Design the Flow ○ Implement the methods with TODO (We still want them to compile 😉) ○ Write the Implementation, make sure the tests pass now https://www.rivu.dev/

Slide 15

Slide 15 text

Let’s do it https://www.rivu.dev/ https://www.rivu.dev/

Slide 16

Slide 16 text

Write the Interface interface ArticlesRepository { fun getArticles(): Flowable> fun fetchAndSyncArticles(): Single> } https://www.rivu.dev/

Slide 17

Slide 17 text

Design the Flow https://www.rivu.dev/ ● getArticles() should return from Local first and then after syncing should return from Remote ○ Should ignore empty data and errors from local and emit from Remote ○ If Remote also fails, should show error

Slide 18

Slide 18 text

Design the Flow https://www.rivu.dev/ ● getArticles() should return from Local first and then after syncing should return from Remote ○ Should ignore empty data and errors from local and emit from Remote ○ If Remote also fails, should show error ● fetchAndSyncArticles() should fetch from Remote, save it in local and emit the data returned from Remote

Slide 19

Slide 19 text

Break down of Steps ● Create Interface https://www.rivu.dev/ interface ArticlesRepository { fun getArticles(): Flowable> fun fetchAndSyncArticles(): Single> }

Slide 20

Slide 20 text

Break down of Steps ● Create Interface ● Create Blank Implementations https://www.rivu.dev/ class ArticlesRepositoryImpl( private val localDataStore: ArticlesDataStore, private val remoteDataStore: ArticlesDataStore ) : ArticlesRepository { override fun getArticles(): Flowable> { TODO("Not yet Implemented") } override fun fetchAndSyncArticles(): Single> { TODO("Not yet Implemented") } }

Slide 21

Slide 21 text

Break down of Steps ● Create Interface ● Create Blank Implementations https://www.rivu.dev/ class ArticlesRepositoryImpl( private val localDataStore: ArticlesDataStore, private val remoteDataStore: ArticlesDataStore ) : ArticlesRepository { override fun getArticles(): Flowable> { TODO("Not yet Implemented") } override fun fetchAndSyncArticles(): Single> { TODO("Not yet Implemented") } }

Slide 22

Slide 22 text

Break down of Steps ● Create Interface ● Create Blank Implementation ● Write Tests https://www.rivu.dev/ Fakes class FakeSuccessDataStore(val data: List): ArticlesDataStore { override fun getArticles(): Single> = Single.defer { Single.just(data) } override fun saveArticles(articleList: List): Completable = Completable.defer { Completable.complete() } } class FakeSaveDataStore: ArticlesDataStore { val articles: MutableList = mutableListOf() override fun getArticles(): Single> = Single.defer { Single.just(emptyList()) } override fun saveArticles(articleList: List): Completable = Completable.fromCallable { articles.clear() articles.addAll(articleList) } } class FakeEmptyDataStore: ArticlesDataStore { override fun getArticles(): Single> = Single.defer { Single.just(emptyList()) } override fun saveArticles(articleList: List): Completable = Completable.defer { Completable.complete() } } class FakeErrorDataStore(val error: Exception): ArticlesDataStore { override fun getArticles(): Single> = Single.defer {

Slide 23

Slide 23 text

Break down of Steps ● Create Interface ● Create Blank Implementation ● Write Tests https://www.rivu.dev/ Fakes class FakeSuccessDataStore(val data: List): ArticlesDataStore { override fun getArticles(): Single> = Single.defer { Single.just(data) } override fun saveArticles(articleList: List): Completable = Completable.defer { Completable.complete() } } class FakeSaveDataStore: ArticlesDataStore { val articles: MutableList = mutableListOf() override fun getArticles(): Single> = Single.defer { Single.just(emptyList()) } override fun saveArticles(articleList: List): Completable = Completable.fromCallable { articles.clear() articles.addAll(articleList) } } class FakeEmptyDataStore: ArticlesDataStore { override fun getArticles(): Single> = Single.defer { Single.just(emptyList()) } override fun saveArticles(articleList: List): Completable = Completable.defer { Completable.complete() } } class FakeErrorDataStore(val error: Exception): ArticlesDataStore { override fun getArticles(): Single> = Single.defer {

Slide 24

Slide 24 text

Break down of Steps ● Create Interface ● Create Blank Implementation ● Write Tests https://www.rivu.dev/ Fakes class FakeSuccessDataStore(val data: List): ArticlesDataStore { override fun getArticles(): Single> = Single.defer { Single.just(data) } override fun saveArticles(articleList: List): Completable = Completable.defer { Completable.complete() } } class FakeSaveDataStore: ArticlesDataStore { val articles: MutableList = mutableListOf() override fun getArticles(): Single> = Single.defer { Single.just(emptyList()) } override fun saveArticles(articleList: List): Completable = Completable.fromCallable { articles.clear() articles.addAll(articleList) } } class FakeEmptyDataStore: ArticlesDataStore { override fun getArticles(): Single> = Single.defer { Single.just(emptyList()) } override fun saveArticles(articleList: List): Completable = Completable.defer { Completable.complete() } } class FakeErrorDataStore(val error: Exception): ArticlesDataStore { override fun getArticles(): Single> = Single.defer {

Slide 25

Slide 25 text

Break down of Steps ● Create Interface ● Create Blank Implementation ● Write Tests https://www.rivu.dev/ Fakes class FakeSaveDataStore: ArticlesDataStore { val articles: MutableList = mutableListOf() override fun getArticles(): Single> = Single.defer { Single.just(emptyList()) } override fun saveArticles(articleList: List): Completable = Completable.fromCallable { articles.clear() articles.addAll(articleList) } } class FakeEmptyDataStore: ArticlesDataStore { override fun getArticles(): Single> = Single.defer { Single.just(emptyList()) } override fun saveArticles(articleList: List): Completable = Completable.defer { Completable.complete() } } class FakeErrorDataStore(val error: Exception): ArticlesDataStore { override fun getArticles(): Single> = Single.defer { Single.error(error) } override fun saveArticles(articleList: List): Completable = Completable.defer { Completable.error(error) } }

Slide 26

Slide 26 text

Break down of Steps ● Create Interface ● Create Blank Implementation ● Write Tests https://www.rivu.dev/ fetchAndSyncArticles @Test fun `fetchAndSyncArticles should emit remote data`() { /*When remote returns data, save it to DB *and emit the same */ //Setup val dummyData = TestDataFactory.dummyArticlesList val localDS = FakeSaveDataStore() val remoteDS = FakeSuccessDataStore(dummyData) val articlesRepo = ArticlesRepositoryImpl(localDS, remoteDS) //When val testObserver = articlesRepo.fetchAndSyncArticles().test() //Then testObserver.assertValue(dummyData)//emitted remote data assertEquals(dummyData, localDS.articles)//saved it to localDS }

Slide 27

Slide 27 text

Break down of Steps ● Create Interface ● Create Blank Implementation ● Write Tests https://www.rivu.dev/ getArticles @Test fun `getArticles should emit EmptyResultSetException`() { /*When both DS emits empty data * It should emit EmptyResultSetException */ //Setup val localDS = FakeEmptyDataStore() val remoteDS = FakeEmptyDataStore() val articlesRepo = ArticlesRepositoryImpl(localDS, remoteDS) //When val testObserver = articlesRepo.getArticles().test() //Then testObserver.assertError(EmptyResultSetException::class.java) }

Slide 28

Slide 28 text

Break down of Steps ● Create Interface ● Create Blank Implementation ● Write Tests https://www.rivu.dev/ getArticles @Test fun `getArticles should emit both data`() { /* When both DS emits data * It should first emit localDS data and then from remoteDS */ //Setup val dummyLocalData = TestDataFactory.dummyArticlesList val dummyRemoteData = TestDataFactory.dummyArticlesList val localDS = FakeSuccessDataStore(dummyLocalData) val remoteDS = FakeSuccessDataStore(dummyRemoteData) val articlesRepo = ArticlesRepositoryImpl(localDS, remoteDS) //When val testObserver = articlesRepo.getArticles().test() //Then testObserver.assertValueCount(2) testObserver.assertValues(dummyLocalData, dummyRemoteData) }

Slide 29

Slide 29 text

Break down of Steps ● Create Interface ● Create Blank Implementation ● Write Tests ● Let them fail https://www.rivu.dev/

Slide 30

Slide 30 text

Break down of Steps ● Create Interface ● Create Blank Implementation ● Write Tests ● Let them fail ● Write the code https://www.rivu.dev/ class ArticlesRepositoryImpl ( private val localDataStore: ArticlesDataStore, private val remoteDataStore: ArticlesDataStore ): ArticlesRepository { override fun fetchAndSyncArticles(): Single> { return remoteDataStore.getArticles() .flatMap { if (it.isNotEmpty()) { localDataStore.saveArticles(it) .andThen(Single.just(it)) } else { Single.error(EmptyResultSetException(EMPTY_DATA_MESSAGE)) } } } override fun getArticles(): Flowable> { return localDataStore.getArticles() .mergeWith(fetchAndSyncArticles()) .onErrorResumeWith(fetchAndSyncArticles()) } }

Slide 31

Slide 31 text

Break down of Steps ● Create Interface ● Create Blank Implementation ● Write Tests ● Let them fail ● Write the code https://www.rivu.dev/ class ArticlesRepositoryImpl ( private val localDataStore: ArticlesDataStore, private val remoteDataStore: ArticlesDataStore ): ArticlesRepository { override fun fetchAndSyncArticles(): Single> { return remoteDataStore.getArticles() .flatMap { if (it.isNotEmpty()) { localDataStore.saveArticles(it) .andThen(Single.just(it)) } else { Single.error(EmptyResultSetException(EMPTY_DATA_MESSAGE)) } } } override fun getArticles(): Flowable> { return localDataStore.getArticles() .mergeWith(fetchAndSyncArticles()) .onErrorResumeWith(fetchAndSyncArticles()) } }

Slide 32

Slide 32 text

Break down of Steps ● Create Interface ● Create Blank Implementation ● Write Tests ● Let them fail ● Write the code ● Run the tests again, see them pass 😎 https://www.rivu.dev/

Slide 33

Slide 33 text

Resources ● https://speakerdeck.com/rivuchk/android-worldwide-tdd-in-android this presentation ● http://agiledata.org/essays/tdd.html Intro to TDD ● https://www.raywenderlich.com/7109-test-driven-development-tutorial-for-android-getting-st arted TDD in Android Tutorial ● https://medium.com/mobility/how-to-do-tdd-in-android-90f013d91d7f TDD in Android Tutorial Series https://www.rivu.dev/

Slide 34

Slide 34 text

Thanks! Rivu Chakraborty Twitter: @rivuchakraborty Website: https://rivu.dev