From 1f45720a1d41f56d8c5e8c1fabcd722a8301b126 Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Thu, 8 Apr 2021 22:45:08 +0200 Subject: [PATCH 01/12] Use Full Dagger Graph for unit tests --- .../com/jraska/github/client/TestUITestApp.kt | 2 - core-testing/build.gradle | 1 + .../github/client/FakeCoreAndroidModule.kt | 47 ++ .../github/client/http/FakeHttpModule.kt | 18 + feature/users/build.gradle | 1 + .../github/client/users/GitHubUserTest.kt | 1 + .../github/client/users/UsersViewModelTest.kt | 23 +- .../client/users/di/TestUsersComponent.kt | 23 + .../resources/response/users_with_extra.json | 622 ++++++++++++++++++ 9 files changed, 720 insertions(+), 18 deletions(-) create mode 100644 core-testing/src/main/java/com/jraska/github/client/FakeCoreAndroidModule.kt create mode 100644 core-testing/src/main/java/com/jraska/github/client/http/FakeHttpModule.kt create mode 100644 feature/users/src/test/java/com/jraska/github/client/users/di/TestUsersComponent.kt create mode 100644 feature/users/src/test/resources/response/users_with_extra.json diff --git a/app/src/androidTest/java/com/jraska/github/client/TestUITestApp.kt b/app/src/androidTest/java/com/jraska/github/client/TestUITestApp.kt index f2be8234..caf05f39 100644 --- a/app/src/androidTest/java/com/jraska/github/client/TestUITestApp.kt +++ b/app/src/androidTest/java/com/jraska/github/client/TestUITestApp.kt @@ -11,8 +11,6 @@ import dagger.Component import javax.inject.Singleton class TestUITestApp : GitHubClientApp(), HasViewModelFactory, HasServiceModelFactory { - val coreModule = FakeCoreModule - private val decoratedServiceFactory by lazy { DecoratedServiceModelFactory(super.serviceModelFactory()) } diff --git a/core-testing/build.gradle b/core-testing/build.gradle index 6fb4be57..aca0d5da 100644 --- a/core-testing/build.gradle +++ b/core-testing/build.gradle @@ -5,6 +5,7 @@ apply plugin: 'kotlin-kapt' dependencies { implementation project(':core-api') + implementation project(':navigation-api') kapt rootProject.ext.daggerAnnotationProcessor implementation rootProject.ext.dagger diff --git a/core-testing/src/main/java/com/jraska/github/client/FakeCoreAndroidModule.kt b/core-testing/src/main/java/com/jraska/github/client/FakeCoreAndroidModule.kt new file mode 100644 index 00000000..6c8b64ea --- /dev/null +++ b/core-testing/src/main/java/com/jraska/github/client/FakeCoreAndroidModule.kt @@ -0,0 +1,47 @@ +package com.jraska.github.client + +import com.jraska.github.client.navigation.Navigator +import com.jraska.github.client.rx.AppSchedulers +import dagger.Module +import dagger.Provides +import okhttp3.HttpUrl +import javax.inject.Singleton + +@Module +object FakeCoreAndroidModule { + @Provides + @Singleton + fun schedulers(): AppSchedulers { + return Fakes.trampoline() + } + + @Provides + @Singleton + fun provideNavigator(): Navigator { + return object : Navigator { + override fun launchOnWeb(httpUrl: HttpUrl) { + TODO("Not yet implemented") + } + + override fun startUserDetail(login: String) { + TODO("Not yet implemented") + } + + override fun startRepoDetail(fullPath: String) { + TODO("Not yet implemented") + } + + override fun showSettings() { + TODO("Not yet implemented") + } + + override fun showAbout() { + TODO("Not yet implemented") + } + + override fun startConsole() { + TODO("Not yet implemented") + } + } + } +} diff --git a/core-testing/src/main/java/com/jraska/github/client/http/FakeHttpModule.kt b/core-testing/src/main/java/com/jraska/github/client/http/FakeHttpModule.kt new file mode 100644 index 00000000..bf5758b5 --- /dev/null +++ b/core-testing/src/main/java/com/jraska/github/client/http/FakeHttpModule.kt @@ -0,0 +1,18 @@ +package com.jraska.github.client.http + +import dagger.Module +import dagger.Provides +import okhttp3.mockwebserver.MockWebServer +import javax.inject.Singleton + +@Module +class FakeHttpModule { + val mockWebServer = MockWebServer() + + @Provides // will be singleton anyway as we have field + fun mockWebServer() = mockWebServer + + @Provides + @Singleton + fun provideRetrofit() = HttpTest.retrofit(mockWebServer.url("/")) +} diff --git a/feature/users/build.gradle b/feature/users/build.gradle index 6d184af2..aed9ba83 100644 --- a/feature/users/build.gradle +++ b/feature/users/build.gradle @@ -51,4 +51,5 @@ dependencies { testImplementation 'androidx.annotation:annotation:1.2.0' testImplementation okHttpMockWebServer testImplementation project(':core-testing') + kaptTest rootProject.ext.daggerAnnotationProcessor } diff --git a/feature/users/src/test/java/com/jraska/github/client/users/GitHubUserTest.kt b/feature/users/src/test/java/com/jraska/github/client/users/GitHubUserTest.kt index 545047b8..e3e835b3 100644 --- a/feature/users/src/test/java/com/jraska/github/client/users/GitHubUserTest.kt +++ b/feature/users/src/test/java/com/jraska/github/client/users/GitHubUserTest.kt @@ -3,6 +3,7 @@ package com.jraska.github.client.users import com.google.gson.Gson import com.google.gson.reflect.TypeToken import com.jraska.github.client.users.model.GitHubUser +import okhttp3.mockwebserver.MockWebServer import org.assertj.core.api.Assertions import org.junit.Test import java.util.ArrayList diff --git a/feature/users/src/test/java/com/jraska/github/client/users/UsersViewModelTest.kt b/feature/users/src/test/java/com/jraska/github/client/users/UsersViewModelTest.kt index 87829ccd..7f27e3ef 100644 --- a/feature/users/src/test/java/com/jraska/github/client/users/UsersViewModelTest.kt +++ b/feature/users/src/test/java/com/jraska/github/client/users/UsersViewModelTest.kt @@ -2,7 +2,9 @@ package com.jraska.github.client.users import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.jraska.github.client.Fakes +import com.jraska.github.client.http.enqueue import com.jraska.github.client.navigation.Navigator +import com.jraska.github.client.users.di.DaggerTestUsersComponent import com.jraska.github.client.users.model.GitHubApiUsersRepository import com.jraska.github.client.users.model.GitHubUser import com.jraska.github.client.users.model.GitHubUsersApi @@ -18,20 +20,15 @@ import org.mockito.MockitoAnnotations class UsersViewModelTest { @get:Rule val testRule = InstantTaskExecutorRule() - @Mock private lateinit var usersApi: GitHubUsersApi - @Mock private lateinit var navigator: Navigator - private lateinit var viewModel: UsersViewModel - private var users = ArrayList() @Before fun setUp() { - MockitoAnnotations.initMocks(this) - - Mockito.`when`(usersApi.getUsers(0)).thenReturn(Single.fromCallable { users }) + val component = DaggerTestUsersComponent.create() + viewModel = component.usersViewModel() - val usersRepository = GitHubApiUsersRepository(usersApi) - viewModel = UsersViewModel(usersRepository, Fakes.trampoline(), navigator, Fakes.emptyAnalytics()) + component.mockWebServer().enqueue("response/users.json") + component.mockWebServer().enqueue("response/users_with_extra.json") } @Test @@ -41,12 +38,6 @@ class UsersViewModelTest { .assertValue { it is UsersViewModel.ViewState.ShowUsers } .assertHistorySize(2) - users.add(GitHubUser().apply { - login = "jraska" - avatarUrl = "https://gihub.com/jraska/avatar.png" - htmlUrl = "https://github.com/jraska" - }) - testObserver.assertValue { it is UsersViewModel.ViewState.ShowUsers } viewModel.onRefresh() @@ -57,6 +48,6 @@ class UsersViewModelTest { .test() .assertNever { it == null } .map { it as UsersViewModel.ViewState.ShowUsers } - .assertValue { it.users.first().login == "jraska" } + .assertValue { it.users.last().login == "jraska" } } } diff --git a/feature/users/src/test/java/com/jraska/github/client/users/di/TestUsersComponent.kt b/feature/users/src/test/java/com/jraska/github/client/users/di/TestUsersComponent.kt new file mode 100644 index 00000000..7590f2cd --- /dev/null +++ b/feature/users/src/test/java/com/jraska/github/client/users/di/TestUsersComponent.kt @@ -0,0 +1,23 @@ +package com.jraska.github.client.users.di + +import com.jraska.github.client.Config +import com.jraska.github.client.FakeConfigModule +import com.jraska.github.client.FakeCoreAndroidModule +import com.jraska.github.client.FakeCoreModule +import com.jraska.github.client.http.FakeHttpModule +import com.jraska.github.client.users.UsersModule +import com.jraska.github.client.users.UsersViewModel +import dagger.Component +import okhttp3.mockwebserver.MockWebServer +import javax.inject.Singleton + +@Singleton +@Component( + modules = [FakeCoreModule::class, FakeConfigModule::class, + UsersModule::class, FakeCoreAndroidModule::class, FakeHttpModule::class] +) +internal interface TestUsersComponent { + fun usersViewModel(): UsersViewModel + + fun mockWebServer(): MockWebServer +} diff --git a/feature/users/src/test/resources/response/users_with_extra.json b/feature/users/src/test/resources/response/users_with_extra.json new file mode 100644 index 00000000..a073fe26 --- /dev/null +++ b/feature/users/src/test/resources/response/users_with_extra.json @@ -0,0 +1,622 @@ +[ + { + "login": "mojombo", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://avatars.githubusercontent.com/u/1?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mojombo", + "html_url": "https://github.com/mojombo", + "followers_url": "https://api.github.com/users/mojombo/followers", + "following_url": "https://api.github.com/users/mojombo/following{/other_user}", + "gists_url": "https://api.github.com/users/mojombo/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mojombo/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mojombo/subscriptions", + "organizations_url": "https://api.github.com/users/mojombo/orgs", + "repos_url": "https://api.github.com/users/mojombo/repos", + "events_url": "https://api.github.com/users/mojombo/events{/privacy}", + "received_events_url": "https://api.github.com/users/mojombo/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "defunkt", + "id": 2, + "node_id": "MDQ6VXNlcjI=", + "avatar_url": "https://avatars0.githubusercontent.com/u/2?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/defunkt", + "html_url": "https://github.com/defunkt", + "followers_url": "https://api.github.com/users/defunkt/followers", + "following_url": "https://api.github.com/users/defunkt/following{/other_user}", + "gists_url": "https://api.github.com/users/defunkt/gists{/gist_id}", + "starred_url": "https://api.github.com/users/defunkt/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/defunkt/subscriptions", + "organizations_url": "https://api.github.com/users/defunkt/orgs", + "repos_url": "https://api.github.com/users/defunkt/repos", + "events_url": "https://api.github.com/users/defunkt/events{/privacy}", + "received_events_url": "https://api.github.com/users/defunkt/received_events", + "type": "User", + "site_admin": true + }, + { + "login": "pjhyett", + "id": 3, + "node_id": "MDQ6VXNlcjM=", + "avatar_url": "https://avatars0.githubusercontent.com/u/3?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/pjhyett", + "html_url": "https://github.com/pjhyett", + "followers_url": "https://api.github.com/users/pjhyett/followers", + "following_url": "https://api.github.com/users/pjhyett/following{/other_user}", + "gists_url": "https://api.github.com/users/pjhyett/gists{/gist_id}", + "starred_url": "https://api.github.com/users/pjhyett/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/pjhyett/subscriptions", + "organizations_url": "https://api.github.com/users/pjhyett/orgs", + "repos_url": "https://api.github.com/users/pjhyett/repos", + "events_url": "https://api.github.com/users/pjhyett/events{/privacy}", + "received_events_url": "https://api.github.com/users/pjhyett/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "wycats", + "id": 4, + "node_id": "MDQ6VXNlcjQ=", + "avatar_url": "https://avatars0.githubusercontent.com/u/4?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/wycats", + "html_url": "https://github.com/wycats", + "followers_url": "https://api.github.com/users/wycats/followers", + "following_url": "https://api.github.com/users/wycats/following{/other_user}", + "gists_url": "https://api.github.com/users/wycats/gists{/gist_id}", + "starred_url": "https://api.github.com/users/wycats/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/wycats/subscriptions", + "organizations_url": "https://api.github.com/users/wycats/orgs", + "repos_url": "https://api.github.com/users/wycats/repos", + "events_url": "https://api.github.com/users/wycats/events{/privacy}", + "received_events_url": "https://api.github.com/users/wycats/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "ezmobius", + "id": 5, + "node_id": "MDQ6VXNlcjU=", + "avatar_url": "https://avatars0.githubusercontent.com/u/5?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/ezmobius", + "html_url": "https://github.com/ezmobius", + "followers_url": "https://api.github.com/users/ezmobius/followers", + "following_url": "https://api.github.com/users/ezmobius/following{/other_user}", + "gists_url": "https://api.github.com/users/ezmobius/gists{/gist_id}", + "starred_url": "https://api.github.com/users/ezmobius/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/ezmobius/subscriptions", + "organizations_url": "https://api.github.com/users/ezmobius/orgs", + "repos_url": "https://api.github.com/users/ezmobius/repos", + "events_url": "https://api.github.com/users/ezmobius/events{/privacy}", + "received_events_url": "https://api.github.com/users/ezmobius/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "ivey", + "id": 6, + "node_id": "MDQ6VXNlcjY=", + "avatar_url": "https://avatars0.githubusercontent.com/u/6?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/ivey", + "html_url": "https://github.com/ivey", + "followers_url": "https://api.github.com/users/ivey/followers", + "following_url": "https://api.github.com/users/ivey/following{/other_user}", + "gists_url": "https://api.github.com/users/ivey/gists{/gist_id}", + "starred_url": "https://api.github.com/users/ivey/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/ivey/subscriptions", + "organizations_url": "https://api.github.com/users/ivey/orgs", + "repos_url": "https://api.github.com/users/ivey/repos", + "events_url": "https://api.github.com/users/ivey/events{/privacy}", + "received_events_url": "https://api.github.com/users/ivey/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "evanphx", + "id": 7, + "node_id": "MDQ6VXNlcjc=", + "avatar_url": "https://avatars0.githubusercontent.com/u/7?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/evanphx", + "html_url": "https://github.com/evanphx", + "followers_url": "https://api.github.com/users/evanphx/followers", + "following_url": "https://api.github.com/users/evanphx/following{/other_user}", + "gists_url": "https://api.github.com/users/evanphx/gists{/gist_id}", + "starred_url": "https://api.github.com/users/evanphx/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/evanphx/subscriptions", + "organizations_url": "https://api.github.com/users/evanphx/orgs", + "repos_url": "https://api.github.com/users/evanphx/repos", + "events_url": "https://api.github.com/users/evanphx/events{/privacy}", + "received_events_url": "https://api.github.com/users/evanphx/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "vanpelt", + "id": 17, + "node_id": "MDQ6VXNlcjE3", + "avatar_url": "https://avatars1.githubusercontent.com/u/17?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vanpelt", + "html_url": "https://github.com/vanpelt", + "followers_url": "https://api.github.com/users/vanpelt/followers", + "following_url": "https://api.github.com/users/vanpelt/following{/other_user}", + "gists_url": "https://api.github.com/users/vanpelt/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vanpelt/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vanpelt/subscriptions", + "organizations_url": "https://api.github.com/users/vanpelt/orgs", + "repos_url": "https://api.github.com/users/vanpelt/repos", + "events_url": "https://api.github.com/users/vanpelt/events{/privacy}", + "received_events_url": "https://api.github.com/users/vanpelt/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "wayneeseguin", + "id": 18, + "node_id": "MDQ6VXNlcjE4", + "avatar_url": "https://avatars0.githubusercontent.com/u/18?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/wayneeseguin", + "html_url": "https://github.com/wayneeseguin", + "followers_url": "https://api.github.com/users/wayneeseguin/followers", + "following_url": "https://api.github.com/users/wayneeseguin/following{/other_user}", + "gists_url": "https://api.github.com/users/wayneeseguin/gists{/gist_id}", + "starred_url": "https://api.github.com/users/wayneeseguin/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/wayneeseguin/subscriptions", + "organizations_url": "https://api.github.com/users/wayneeseguin/orgs", + "repos_url": "https://api.github.com/users/wayneeseguin/repos", + "events_url": "https://api.github.com/users/wayneeseguin/events{/privacy}", + "received_events_url": "https://api.github.com/users/wayneeseguin/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "brynary", + "id": 19, + "node_id": "MDQ6VXNlcjE5", + "avatar_url": "https://avatars0.githubusercontent.com/u/19?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/brynary", + "html_url": "https://github.com/brynary", + "followers_url": "https://api.github.com/users/brynary/followers", + "following_url": "https://api.github.com/users/brynary/following{/other_user}", + "gists_url": "https://api.github.com/users/brynary/gists{/gist_id}", + "starred_url": "https://api.github.com/users/brynary/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/brynary/subscriptions", + "organizations_url": "https://api.github.com/users/brynary/orgs", + "repos_url": "https://api.github.com/users/brynary/repos", + "events_url": "https://api.github.com/users/brynary/events{/privacy}", + "received_events_url": "https://api.github.com/users/brynary/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "kevinclark", + "id": 20, + "node_id": "MDQ6VXNlcjIw", + "avatar_url": "https://avatars3.githubusercontent.com/u/20?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/kevinclark", + "html_url": "https://github.com/kevinclark", + "followers_url": "https://api.github.com/users/kevinclark/followers", + "following_url": "https://api.github.com/users/kevinclark/following{/other_user}", + "gists_url": "https://api.github.com/users/kevinclark/gists{/gist_id}", + "starred_url": "https://api.github.com/users/kevinclark/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/kevinclark/subscriptions", + "organizations_url": "https://api.github.com/users/kevinclark/orgs", + "repos_url": "https://api.github.com/users/kevinclark/repos", + "events_url": "https://api.github.com/users/kevinclark/events{/privacy}", + "received_events_url": "https://api.github.com/users/kevinclark/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "technoweenie", + "id": 21, + "node_id": "MDQ6VXNlcjIx", + "avatar_url": "https://avatars3.githubusercontent.com/u/21?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/technoweenie", + "html_url": "https://github.com/technoweenie", + "followers_url": "https://api.github.com/users/technoweenie/followers", + "following_url": "https://api.github.com/users/technoweenie/following{/other_user}", + "gists_url": "https://api.github.com/users/technoweenie/gists{/gist_id}", + "starred_url": "https://api.github.com/users/technoweenie/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/technoweenie/subscriptions", + "organizations_url": "https://api.github.com/users/technoweenie/orgs", + "repos_url": "https://api.github.com/users/technoweenie/repos", + "events_url": "https://api.github.com/users/technoweenie/events{/privacy}", + "received_events_url": "https://api.github.com/users/technoweenie/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "macournoyer", + "id": 22, + "node_id": "MDQ6VXNlcjIy", + "avatar_url": "https://avatars3.githubusercontent.com/u/22?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/macournoyer", + "html_url": "https://github.com/macournoyer", + "followers_url": "https://api.github.com/users/macournoyer/followers", + "following_url": "https://api.github.com/users/macournoyer/following{/other_user}", + "gists_url": "https://api.github.com/users/macournoyer/gists{/gist_id}", + "starred_url": "https://api.github.com/users/macournoyer/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/macournoyer/subscriptions", + "organizations_url": "https://api.github.com/users/macournoyer/orgs", + "repos_url": "https://api.github.com/users/macournoyer/repos", + "events_url": "https://api.github.com/users/macournoyer/events{/privacy}", + "received_events_url": "https://api.github.com/users/macournoyer/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "takeo", + "id": 23, + "node_id": "MDQ6VXNlcjIz", + "avatar_url": "https://avatars3.githubusercontent.com/u/23?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/takeo", + "html_url": "https://github.com/takeo", + "followers_url": "https://api.github.com/users/takeo/followers", + "following_url": "https://api.github.com/users/takeo/following{/other_user}", + "gists_url": "https://api.github.com/users/takeo/gists{/gist_id}", + "starred_url": "https://api.github.com/users/takeo/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/takeo/subscriptions", + "organizations_url": "https://api.github.com/users/takeo/orgs", + "repos_url": "https://api.github.com/users/takeo/repos", + "events_url": "https://api.github.com/users/takeo/events{/privacy}", + "received_events_url": "https://api.github.com/users/takeo/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "Caged", + "id": 25, + "node_id": "MDQ6VXNlcjI1", + "avatar_url": "https://avatars3.githubusercontent.com/u/25?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Caged", + "html_url": "https://github.com/Caged", + "followers_url": "https://api.github.com/users/Caged/followers", + "following_url": "https://api.github.com/users/Caged/following{/other_user}", + "gists_url": "https://api.github.com/users/Caged/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Caged/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Caged/subscriptions", + "organizations_url": "https://api.github.com/users/Caged/orgs", + "repos_url": "https://api.github.com/users/Caged/repos", + "events_url": "https://api.github.com/users/Caged/events{/privacy}", + "received_events_url": "https://api.github.com/users/Caged/received_events", + "type": "User", + "site_admin": true + }, + { + "login": "topfunky", + "id": 26, + "node_id": "MDQ6VXNlcjI2", + "avatar_url": "https://avatars3.githubusercontent.com/u/26?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/topfunky", + "html_url": "https://github.com/topfunky", + "followers_url": "https://api.github.com/users/topfunky/followers", + "following_url": "https://api.github.com/users/topfunky/following{/other_user}", + "gists_url": "https://api.github.com/users/topfunky/gists{/gist_id}", + "starred_url": "https://api.github.com/users/topfunky/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/topfunky/subscriptions", + "organizations_url": "https://api.github.com/users/topfunky/orgs", + "repos_url": "https://api.github.com/users/topfunky/repos", + "events_url": "https://api.github.com/users/topfunky/events{/privacy}", + "received_events_url": "https://api.github.com/users/topfunky/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "anotherjesse", + "id": 27, + "node_id": "MDQ6VXNlcjI3", + "avatar_url": "https://avatars3.githubusercontent.com/u/27?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/anotherjesse", + "html_url": "https://github.com/anotherjesse", + "followers_url": "https://api.github.com/users/anotherjesse/followers", + "following_url": "https://api.github.com/users/anotherjesse/following{/other_user}", + "gists_url": "https://api.github.com/users/anotherjesse/gists{/gist_id}", + "starred_url": "https://api.github.com/users/anotherjesse/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/anotherjesse/subscriptions", + "organizations_url": "https://api.github.com/users/anotherjesse/orgs", + "repos_url": "https://api.github.com/users/anotherjesse/repos", + "events_url": "https://api.github.com/users/anotherjesse/events{/privacy}", + "received_events_url": "https://api.github.com/users/anotherjesse/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "roland", + "id": 28, + "node_id": "MDQ6VXNlcjI4", + "avatar_url": "https://avatars2.githubusercontent.com/u/28?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/roland", + "html_url": "https://github.com/roland", + "followers_url": "https://api.github.com/users/roland/followers", + "following_url": "https://api.github.com/users/roland/following{/other_user}", + "gists_url": "https://api.github.com/users/roland/gists{/gist_id}", + "starred_url": "https://api.github.com/users/roland/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/roland/subscriptions", + "organizations_url": "https://api.github.com/users/roland/orgs", + "repos_url": "https://api.github.com/users/roland/repos", + "events_url": "https://api.github.com/users/roland/events{/privacy}", + "received_events_url": "https://api.github.com/users/roland/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "lukas", + "id": 29, + "node_id": "MDQ6VXNlcjI5", + "avatar_url": "https://avatars2.githubusercontent.com/u/29?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/lukas", + "html_url": "https://github.com/lukas", + "followers_url": "https://api.github.com/users/lukas/followers", + "following_url": "https://api.github.com/users/lukas/following{/other_user}", + "gists_url": "https://api.github.com/users/lukas/gists{/gist_id}", + "starred_url": "https://api.github.com/users/lukas/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/lukas/subscriptions", + "organizations_url": "https://api.github.com/users/lukas/orgs", + "repos_url": "https://api.github.com/users/lukas/repos", + "events_url": "https://api.github.com/users/lukas/events{/privacy}", + "received_events_url": "https://api.github.com/users/lukas/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "fanvsfan", + "id": 30, + "node_id": "MDQ6VXNlcjMw", + "avatar_url": "https://avatars2.githubusercontent.com/u/30?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/fanvsfan", + "html_url": "https://github.com/fanvsfan", + "followers_url": "https://api.github.com/users/fanvsfan/followers", + "following_url": "https://api.github.com/users/fanvsfan/following{/other_user}", + "gists_url": "https://api.github.com/users/fanvsfan/gists{/gist_id}", + "starred_url": "https://api.github.com/users/fanvsfan/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/fanvsfan/subscriptions", + "organizations_url": "https://api.github.com/users/fanvsfan/orgs", + "repos_url": "https://api.github.com/users/fanvsfan/repos", + "events_url": "https://api.github.com/users/fanvsfan/events{/privacy}", + "received_events_url": "https://api.github.com/users/fanvsfan/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "tomtt", + "id": 31, + "node_id": "MDQ6VXNlcjMx", + "avatar_url": "https://avatars2.githubusercontent.com/u/31?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/tomtt", + "html_url": "https://github.com/tomtt", + "followers_url": "https://api.github.com/users/tomtt/followers", + "following_url": "https://api.github.com/users/tomtt/following{/other_user}", + "gists_url": "https://api.github.com/users/tomtt/gists{/gist_id}", + "starred_url": "https://api.github.com/users/tomtt/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/tomtt/subscriptions", + "organizations_url": "https://api.github.com/users/tomtt/orgs", + "repos_url": "https://api.github.com/users/tomtt/repos", + "events_url": "https://api.github.com/users/tomtt/events{/privacy}", + "received_events_url": "https://api.github.com/users/tomtt/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "railsjitsu", + "id": 32, + "node_id": "MDQ6VXNlcjMy", + "avatar_url": "https://avatars2.githubusercontent.com/u/32?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/railsjitsu", + "html_url": "https://github.com/railsjitsu", + "followers_url": "https://api.github.com/users/railsjitsu/followers", + "following_url": "https://api.github.com/users/railsjitsu/following{/other_user}", + "gists_url": "https://api.github.com/users/railsjitsu/gists{/gist_id}", + "starred_url": "https://api.github.com/users/railsjitsu/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/railsjitsu/subscriptions", + "organizations_url": "https://api.github.com/users/railsjitsu/orgs", + "repos_url": "https://api.github.com/users/railsjitsu/repos", + "events_url": "https://api.github.com/users/railsjitsu/events{/privacy}", + "received_events_url": "https://api.github.com/users/railsjitsu/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "nitay", + "id": 34, + "node_id": "MDQ6VXNlcjM0", + "avatar_url": "https://avatars2.githubusercontent.com/u/34?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/nitay", + "html_url": "https://github.com/nitay", + "followers_url": "https://api.github.com/users/nitay/followers", + "following_url": "https://api.github.com/users/nitay/following{/other_user}", + "gists_url": "https://api.github.com/users/nitay/gists{/gist_id}", + "starred_url": "https://api.github.com/users/nitay/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/nitay/subscriptions", + "organizations_url": "https://api.github.com/users/nitay/orgs", + "repos_url": "https://api.github.com/users/nitay/repos", + "events_url": "https://api.github.com/users/nitay/events{/privacy}", + "received_events_url": "https://api.github.com/users/nitay/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "kevwil", + "id": 35, + "node_id": "MDQ6VXNlcjM1", + "avatar_url": "https://avatars2.githubusercontent.com/u/35?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/kevwil", + "html_url": "https://github.com/kevwil", + "followers_url": "https://api.github.com/users/kevwil/followers", + "following_url": "https://api.github.com/users/kevwil/following{/other_user}", + "gists_url": "https://api.github.com/users/kevwil/gists{/gist_id}", + "starred_url": "https://api.github.com/users/kevwil/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/kevwil/subscriptions", + "organizations_url": "https://api.github.com/users/kevwil/orgs", + "repos_url": "https://api.github.com/users/kevwil/repos", + "events_url": "https://api.github.com/users/kevwil/events{/privacy}", + "received_events_url": "https://api.github.com/users/kevwil/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "KirinDave", + "id": 36, + "node_id": "MDQ6VXNlcjM2", + "avatar_url": "https://avatars2.githubusercontent.com/u/36?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/KirinDave", + "html_url": "https://github.com/KirinDave", + "followers_url": "https://api.github.com/users/KirinDave/followers", + "following_url": "https://api.github.com/users/KirinDave/following{/other_user}", + "gists_url": "https://api.github.com/users/KirinDave/gists{/gist_id}", + "starred_url": "https://api.github.com/users/KirinDave/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/KirinDave/subscriptions", + "organizations_url": "https://api.github.com/users/KirinDave/orgs", + "repos_url": "https://api.github.com/users/KirinDave/repos", + "events_url": "https://api.github.com/users/KirinDave/events{/privacy}", + "received_events_url": "https://api.github.com/users/KirinDave/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "jamesgolick", + "id": 37, + "node_id": "MDQ6VXNlcjM3", + "avatar_url": "https://avatars2.githubusercontent.com/u/37?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/jamesgolick", + "html_url": "https://github.com/jamesgolick", + "followers_url": "https://api.github.com/users/jamesgolick/followers", + "following_url": "https://api.github.com/users/jamesgolick/following{/other_user}", + "gists_url": "https://api.github.com/users/jamesgolick/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jamesgolick/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jamesgolick/subscriptions", + "organizations_url": "https://api.github.com/users/jamesgolick/orgs", + "repos_url": "https://api.github.com/users/jamesgolick/repos", + "events_url": "https://api.github.com/users/jamesgolick/events{/privacy}", + "received_events_url": "https://api.github.com/users/jamesgolick/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "atmos", + "id": 38, + "node_id": "MDQ6VXNlcjM4", + "avatar_url": "https://avatars3.githubusercontent.com/u/38?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/atmos", + "html_url": "https://github.com/atmos", + "followers_url": "https://api.github.com/users/atmos/followers", + "following_url": "https://api.github.com/users/atmos/following{/other_user}", + "gists_url": "https://api.github.com/users/atmos/gists{/gist_id}", + "starred_url": "https://api.github.com/users/atmos/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/atmos/subscriptions", + "organizations_url": "https://api.github.com/users/atmos/orgs", + "repos_url": "https://api.github.com/users/atmos/repos", + "events_url": "https://api.github.com/users/atmos/events{/privacy}", + "received_events_url": "https://api.github.com/users/atmos/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "errfree", + "id": 44, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ0", + "avatar_url": "https://avatars2.githubusercontent.com/u/44?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/errfree", + "html_url": "https://github.com/errfree", + "followers_url": "https://api.github.com/users/errfree/followers", + "following_url": "https://api.github.com/users/errfree/following{/other_user}", + "gists_url": "https://api.github.com/users/errfree/gists{/gist_id}", + "starred_url": "https://api.github.com/users/errfree/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/errfree/subscriptions", + "organizations_url": "https://api.github.com/users/errfree/orgs", + "repos_url": "https://api.github.com/users/errfree/repos", + "events_url": "https://api.github.com/users/errfree/events{/privacy}", + "received_events_url": "https://api.github.com/users/errfree/received_events", + "type": "Organization", + "site_admin": false + }, + { + "login": "mojodna", + "id": 45, + "node_id": "MDQ6VXNlcjQ1", + "avatar_url": "https://avatars2.githubusercontent.com/u/45?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mojodna", + "html_url": "https://github.com/mojodna", + "followers_url": "https://api.github.com/users/mojodna/followers", + "following_url": "https://api.github.com/users/mojodna/following{/other_user}", + "gists_url": "https://api.github.com/users/mojodna/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mojodna/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mojodna/subscriptions", + "organizations_url": "https://api.github.com/users/mojodna/orgs", + "repos_url": "https://api.github.com/users/mojodna/repos", + "events_url": "https://api.github.com/users/mojodna/events{/privacy}", + "received_events_url": "https://api.github.com/users/mojodna/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "bmizerany", + "id": 46, + "node_id": "MDQ6VXNlcjQ2", + "avatar_url": "https://avatars2.githubusercontent.com/u/46?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bmizerany", + "html_url": "https://github.com/bmizerany", + "followers_url": "https://api.github.com/users/bmizerany/followers", + "following_url": "https://api.github.com/users/bmizerany/following{/other_user}", + "gists_url": "https://api.github.com/users/bmizerany/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bmizerany/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bmizerany/subscriptions", + "organizations_url": "https://api.github.com/users/bmizerany/orgs", + "repos_url": "https://api.github.com/users/bmizerany/repos", + "events_url": "https://api.github.com/users/bmizerany/events{/privacy}", + "received_events_url": "https://api.github.com/users/bmizerany/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "jraska", + "id": 6277721, + "node_id": "MDQ6VXNlcjYyNzc3MjE=", + "avatar_url": "https://avatars.githubusercontent.com/u/6277721?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/jraska", + "html_url": "https://github.com/jraska", + "followers_url": "https://api.github.com/users/jraska/followers", + "following_url": "https://api.github.com/users/jraska/following{/other_user}", + "gists_url": "https://api.github.com/users/jraska/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jraska/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jraska/subscriptions", + "organizations_url": "https://api.github.com/users/jraska/orgs", + "repos_url": "https://api.github.com/users/jraska/repos", + "events_url": "https://api.github.com/users/jraska/events{/privacy}", + "received_events_url": "https://api.github.com/users/jraska/received_events", + "type": "User", + "site_admin": false + } +] From 52fd7b3f3043884b83d75b761a624890a7d0c573 Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Thu, 8 Apr 2021 23:00:19 +0200 Subject: [PATCH 02/12] Add FakeNavigator and consolidate coe fake modules --- .../github/client/FakeCoreAndroidModule.kt | 26 +-------------- .../com/jraska/github/client/FakeModules.kt | 14 ++++++++ .../java/com/jraska/github/client/Fakes.kt | 4 +++ .../github/client/RecordingNavigator.kt | 32 +++++++++++++++++++ .../client/users/di/TestUsersComponent.kt | 9 ++---- 5 files changed, 53 insertions(+), 32 deletions(-) create mode 100644 core-testing/src/main/java/com/jraska/github/client/FakeModules.kt create mode 100644 core-testing/src/main/java/com/jraska/github/client/RecordingNavigator.kt diff --git a/core-testing/src/main/java/com/jraska/github/client/FakeCoreAndroidModule.kt b/core-testing/src/main/java/com/jraska/github/client/FakeCoreAndroidModule.kt index 6c8b64ea..b1cd1cd5 100644 --- a/core-testing/src/main/java/com/jraska/github/client/FakeCoreAndroidModule.kt +++ b/core-testing/src/main/java/com/jraska/github/client/FakeCoreAndroidModule.kt @@ -18,30 +18,6 @@ object FakeCoreAndroidModule { @Provides @Singleton fun provideNavigator(): Navigator { - return object : Navigator { - override fun launchOnWeb(httpUrl: HttpUrl) { - TODO("Not yet implemented") - } - - override fun startUserDetail(login: String) { - TODO("Not yet implemented") - } - - override fun startRepoDetail(fullPath: String) { - TODO("Not yet implemented") - } - - override fun showSettings() { - TODO("Not yet implemented") - } - - override fun showAbout() { - TODO("Not yet implemented") - } - - override fun startConsole() { - TODO("Not yet implemented") - } - } + return Fakes.recordingNavigator() } } diff --git a/core-testing/src/main/java/com/jraska/github/client/FakeModules.kt b/core-testing/src/main/java/com/jraska/github/client/FakeModules.kt new file mode 100644 index 00000000..b8716c60 --- /dev/null +++ b/core-testing/src/main/java/com/jraska/github/client/FakeModules.kt @@ -0,0 +1,14 @@ +package com.jraska.github.client + +import com.jraska.github.client.http.FakeHttpModule +import dagger.Module + +@Module( + includes = arrayOf( + FakeCoreModule::class, + FakeCoreAndroidModule::class, + FakeConfigModule::class, + FakeHttpModule::class, + ) +) +object FakeModules diff --git a/core-testing/src/main/java/com/jraska/github/client/Fakes.kt b/core-testing/src/main/java/com/jraska/github/client/Fakes.kt index 2a92cc22..fd19f521 100644 --- a/core-testing/src/main/java/com/jraska/github/client/Fakes.kt +++ b/core-testing/src/main/java/com/jraska/github/client/Fakes.kt @@ -29,4 +29,8 @@ object Fakes { fun recordingAnalyticsProperty(): RecordingAnalyticsProperty { return RecordingAnalyticsProperty() } + + fun recordingNavigator() : RecordingNavigator { + return RecordingNavigator() + } } diff --git a/core-testing/src/main/java/com/jraska/github/client/RecordingNavigator.kt b/core-testing/src/main/java/com/jraska/github/client/RecordingNavigator.kt new file mode 100644 index 00000000..81a61300 --- /dev/null +++ b/core-testing/src/main/java/com/jraska/github/client/RecordingNavigator.kt @@ -0,0 +1,32 @@ +package com.jraska.github.client + +import com.jraska.github.client.navigation.Navigator +import okhttp3.HttpUrl + +class RecordingNavigator: Navigator { + val screensStarted: MutableList = mutableListOf() + + override fun launchOnWeb(httpUrl: HttpUrl) { + screensStarted.add(httpUrl) + } + + override fun startUserDetail(login: String) { + screensStarted.add(login) + } + + override fun startRepoDetail(fullPath: String) { + screensStarted.add(fullPath) + } + + override fun showSettings() { + screensStarted.add("settings") + } + + override fun showAbout() { + screensStarted.add("about") + } + + override fun startConsole() { + screensStarted.add("console") + } +} diff --git a/feature/users/src/test/java/com/jraska/github/client/users/di/TestUsersComponent.kt b/feature/users/src/test/java/com/jraska/github/client/users/di/TestUsersComponent.kt index 7590f2cd..77244e42 100644 --- a/feature/users/src/test/java/com/jraska/github/client/users/di/TestUsersComponent.kt +++ b/feature/users/src/test/java/com/jraska/github/client/users/di/TestUsersComponent.kt @@ -1,10 +1,6 @@ package com.jraska.github.client.users.di -import com.jraska.github.client.Config -import com.jraska.github.client.FakeConfigModule -import com.jraska.github.client.FakeCoreAndroidModule -import com.jraska.github.client.FakeCoreModule -import com.jraska.github.client.http.FakeHttpModule +import com.jraska.github.client.FakeModules import com.jraska.github.client.users.UsersModule import com.jraska.github.client.users.UsersViewModel import dagger.Component @@ -13,8 +9,7 @@ import javax.inject.Singleton @Singleton @Component( - modules = [FakeCoreModule::class, FakeConfigModule::class, - UsersModule::class, FakeCoreAndroidModule::class, FakeHttpModule::class] + modules = [FakeModules::class, UsersModule::class] ) internal interface TestUsersComponent { fun usersViewModel(): UsersViewModel From 71aa1fa5928d72f0cd73a98c423d7988c19ac24e Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Thu, 8 Apr 2021 23:35:12 +0200 Subject: [PATCH 03/12] Write some integration ViewModel tests --- .../client/users/UserDetailViewModelTest.kt | 84 +++-- .../github/client/users/UsersViewModelTest.kt | 9 - .../client/users/di/TestUsersComponent.kt | 3 + .../src/test/resources/response/error.json | 4 + .../src/test/resources/response/jraska.json | 34 ++ .../test/resources/response/jraska_repos.json | 302 ++++++++++++++++++ 6 files changed, 394 insertions(+), 42 deletions(-) create mode 100644 feature/users/src/test/resources/response/error.json create mode 100644 feature/users/src/test/resources/response/jraska.json create mode 100644 feature/users/src/test/resources/response/jraska_repos.json diff --git a/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt b/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt index 254790ee..c6c44122 100644 --- a/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt +++ b/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt @@ -1,70 +1,88 @@ package com.jraska.github.client.users import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import com.jraska.github.client.Fakes -import com.jraska.github.client.navigation.Navigator +import com.jraska.github.client.http.enqueue +import com.jraska.github.client.http.onUrlPartReturn +import com.jraska.github.client.http.onUrlReturn +import com.jraska.github.client.users.di.DaggerTestUsersComponent +import com.jraska.github.client.users.model.RepoHeader import com.jraska.github.client.users.model.User import com.jraska.github.client.users.model.UserDetail import com.jraska.github.client.users.model.UserStats -import com.jraska.github.client.users.model.UsersRepository import com.jraska.livedata.test -import io.reactivex.Observable +import okhttp3.mockwebserver.MockWebServer +import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mockito.Mockito.`when` -import org.mockito.Mockito.mock -import org.mockito.Mockito.verify import org.threeten.bp.Instant class UserDetailViewModelTest { - @get:Rule val testRule = InstantTaskExecutorRule() + @get:Rule + val testRule = InstantTaskExecutorRule() - private lateinit var usersRepository: UsersRepository private lateinit var viewModel: UserDetailViewModel + private lateinit var mockWebServer: MockWebServer @Before fun before() { - usersRepository = mock(UsersRepository::class.java) - - val detailObservable = Observable.just(testDetail()) - `when`(usersRepository.getUserDetail("someLogin", 3)).thenReturn(detailObservable) - `when`(usersRepository.getUserDetail("different", 3)).thenReturn(detailObservable) - val config = Fakes.config(mapOf("user_detail_section_size" to 3L)) - - viewModel = UserDetailViewModel( - usersRepository, Fakes.trampoline(), - mock(Navigator::class.java), Fakes.emptyAnalytics(), config - ) + val component = DaggerTestUsersComponent.create() + viewModel = component.userDetailViewModel() + mockWebServer = component.mockWebServer() } @Test fun whenLiveData_thenCorrectUser() { - viewModel.userDetail("someLogin") + mockWebServer.onUrlReturn(".*/users/jraska".toRegex(), "response/jraska.json") + mockWebServer.onUrlPartReturn("users/jraska/repos", "response/jraska_repos.json") + + val displayUser = viewModel.userDetail("jraska") .test() - .assertHasValue() - .assertValue { it is UserDetailViewModel.ViewState.DisplayUser } + .assertHistorySize(2) + .value() as UserDetailViewModel.ViewState.DisplayUser + + assertThat(displayUser.user).usingRecursiveComparison().isEqualTo(testDetail()) + } + + @Test + fun whenError_thenErrorStateDisplayed() { + mockWebServer.enqueue("response/error.json") + mockWebServer.enqueue("response/error.json") + + val value = viewModel.userDetail("jraska").test().value() + + assertThat(value).isInstanceOf(UserDetailViewModel.ViewState.Error::class.java) } @Test fun whenSameLoginMultipleTimes_thenOnlyOneObservableCreated() { - val login = "someLogin" + mockWebServer.onUrlReturn(".*/users/mojombo".toRegex(), "response/mojombo.json") + mockWebServer.onUrlPartReturn("users/mojombo/repos", "response/mojombo_repos.json") - viewModel.userDetail(login).test() - verify(usersRepository).getUserDetail("someLogin", 3) + mockWebServer.onUrlReturn(".*/users/jraska".toRegex(), "response/jraska.json") + mockWebServer.onUrlPartReturn("users/jraska/repos", "response/jraska_repos.json") - viewModel.userDetail(login).test() - verify(usersRepository).getUserDetail("someLogin", 3) + viewModel.userDetail("mojombo").test() + viewModel.userDetail("mojombo").test() - viewModel.userDetail("different").test() - verify(usersRepository).getUserDetail("different", 3) + assertThat(mockWebServer.requestCount).isEqualTo(2) + + viewModel.userDetail("jraska").test() + assertThat(mockWebServer.requestCount).isEqualTo(4) } companion object { internal fun testDetail(): UserDetail { - val user = User("someLogin", "url", true) - val stats = UserStats(0, 0, 0, Instant.MIN) - return UserDetail(user, stats, emptyList(), emptyList()) + val user = User("jraska", "https://avatars.githubusercontent.com/u/6277721?v=4", false) + val stats = UserStats(74, 2, 17, Instant.parse("2013-12-28T19:43:19Z")) + + val repos = listOf( + RepoHeader("jraska", "Console", "https://github.com/jraska/Console", 107, 12), + RepoHeader("jraska", "Dagger-Codelab", "https://github.com/jraska/Dagger-Codelab", 8, 5), + RepoHeader("jraska", "TimerViews", "https://github.com/jraska/TimerViews", 2, 3), + ) + + return UserDetail(user, stats, repos, emptyList()) } } } diff --git a/feature/users/src/test/java/com/jraska/github/client/users/UsersViewModelTest.kt b/feature/users/src/test/java/com/jraska/github/client/users/UsersViewModelTest.kt index 7f27e3ef..41103a38 100644 --- a/feature/users/src/test/java/com/jraska/github/client/users/UsersViewModelTest.kt +++ b/feature/users/src/test/java/com/jraska/github/client/users/UsersViewModelTest.kt @@ -1,21 +1,12 @@ package com.jraska.github.client.users import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import com.jraska.github.client.Fakes import com.jraska.github.client.http.enqueue -import com.jraska.github.client.navigation.Navigator import com.jraska.github.client.users.di.DaggerTestUsersComponent -import com.jraska.github.client.users.model.GitHubApiUsersRepository -import com.jraska.github.client.users.model.GitHubUser -import com.jraska.github.client.users.model.GitHubUsersApi import com.jraska.livedata.test -import io.reactivex.Single import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.MockitoAnnotations class UsersViewModelTest { @get:Rule val testRule = InstantTaskExecutorRule() diff --git a/feature/users/src/test/java/com/jraska/github/client/users/di/TestUsersComponent.kt b/feature/users/src/test/java/com/jraska/github/client/users/di/TestUsersComponent.kt index 77244e42..f4449b7d 100644 --- a/feature/users/src/test/java/com/jraska/github/client/users/di/TestUsersComponent.kt +++ b/feature/users/src/test/java/com/jraska/github/client/users/di/TestUsersComponent.kt @@ -1,6 +1,7 @@ package com.jraska.github.client.users.di import com.jraska.github.client.FakeModules +import com.jraska.github.client.users.UserDetailViewModel import com.jraska.github.client.users.UsersModule import com.jraska.github.client.users.UsersViewModel import dagger.Component @@ -14,5 +15,7 @@ import javax.inject.Singleton internal interface TestUsersComponent { fun usersViewModel(): UsersViewModel + fun userDetailViewModel(): UserDetailViewModel + fun mockWebServer(): MockWebServer } diff --git a/feature/users/src/test/resources/response/error.json b/feature/users/src/test/resources/response/error.json new file mode 100644 index 00000000..3a24768f --- /dev/null +++ b/feature/users/src/test/resources/response/error.json @@ -0,0 +1,4 @@ +{ + "message": "Not Found", + "documentation_url": "https://docs.github.com/rest" +} diff --git a/feature/users/src/test/resources/response/jraska.json b/feature/users/src/test/resources/response/jraska.json new file mode 100644 index 00000000..69124687 --- /dev/null +++ b/feature/users/src/test/resources/response/jraska.json @@ -0,0 +1,34 @@ +{ + "login": "jraska", + "id": 6277721, + "node_id": "MDQ6VXNlcjYyNzc3MjE=", + "avatar_url": "https://avatars.githubusercontent.com/u/6277721?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/jraska", + "html_url": "https://github.com/jraska", + "followers_url": "https://api.github.com/users/jraska/followers", + "following_url": "https://api.github.com/users/jraska/following{/other_user}", + "gists_url": "https://api.github.com/users/jraska/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jraska/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jraska/subscriptions", + "organizations_url": "https://api.github.com/users/jraska/orgs", + "repos_url": "https://api.github.com/users/jraska/repos", + "events_url": "https://api.github.com/users/jraska/events{/privacy}", + "received_events_url": "https://api.github.com/users/jraska/received_events", + "type": "User", + "site_admin": false, + "name": "Josef Raska", + "company": null, + "blog": "jraska.com", + "location": null, + "email": null, + "hireable": null, + "bio": "Android Engineer, technology fan, code lover and more.", + "twitter_username": null, + "public_repos": 17, + "public_gists": 31, + "followers": 74, + "following": 2, + "created_at": "2013-12-28T19:43:19Z", + "updated_at": "2021-04-08T20:45:11Z" +} diff --git a/feature/users/src/test/resources/response/jraska_repos.json b/feature/users/src/test/resources/response/jraska_repos.json new file mode 100644 index 00000000..f6a767db --- /dev/null +++ b/feature/users/src/test/resources/response/jraska_repos.json @@ -0,0 +1,302 @@ +[ + { + "id": 46227952, + "node_id": "MDEwOlJlcG9zaXRvcnk0NjIyNzk1Mg==", + "name": "Console", + "full_name": "jraska/Console", + "private": false, + "owner": { + "login": "jraska", + "id": 6277721, + "node_id": "MDQ6VXNlcjYyNzc3MjE=", + "avatar_url": "https://avatars.githubusercontent.com/u/6277721?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/jraska", + "html_url": "https://github.com/jraska", + "followers_url": "https://api.github.com/users/jraska/followers", + "following_url": "https://api.github.com/users/jraska/following{/other_user}", + "gists_url": "https://api.github.com/users/jraska/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jraska/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jraska/subscriptions", + "organizations_url": "https://api.github.com/users/jraska/orgs", + "repos_url": "https://api.github.com/users/jraska/repos", + "events_url": "https://api.github.com/users/jraska/events{/privacy}", + "received_events_url": "https://api.github.com/users/jraska/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/jraska/Console", + "description": "Android console implementation", + "fork": false, + "url": "https://api.github.com/repos/jraska/Console", + "forks_url": "https://api.github.com/repos/jraska/Console/forks", + "keys_url": "https://api.github.com/repos/jraska/Console/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/jraska/Console/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/jraska/Console/teams", + "hooks_url": "https://api.github.com/repos/jraska/Console/hooks", + "issue_events_url": "https://api.github.com/repos/jraska/Console/issues/events{/number}", + "events_url": "https://api.github.com/repos/jraska/Console/events", + "assignees_url": "https://api.github.com/repos/jraska/Console/assignees{/user}", + "branches_url": "https://api.github.com/repos/jraska/Console/branches{/branch}", + "tags_url": "https://api.github.com/repos/jraska/Console/tags", + "blobs_url": "https://api.github.com/repos/jraska/Console/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/jraska/Console/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/jraska/Console/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/jraska/Console/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/jraska/Console/statuses/{sha}", + "languages_url": "https://api.github.com/repos/jraska/Console/languages", + "stargazers_url": "https://api.github.com/repos/jraska/Console/stargazers", + "contributors_url": "https://api.github.com/repos/jraska/Console/contributors", + "subscribers_url": "https://api.github.com/repos/jraska/Console/subscribers", + "subscription_url": "https://api.github.com/repos/jraska/Console/subscription", + "commits_url": "https://api.github.com/repos/jraska/Console/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/jraska/Console/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/jraska/Console/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/jraska/Console/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/jraska/Console/contents/{+path}", + "compare_url": "https://api.github.com/repos/jraska/Console/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/jraska/Console/merges", + "archive_url": "https://api.github.com/repos/jraska/Console/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/jraska/Console/downloads", + "issues_url": "https://api.github.com/repos/jraska/Console/issues{/number}", + "pulls_url": "https://api.github.com/repos/jraska/Console/pulls{/number}", + "milestones_url": "https://api.github.com/repos/jraska/Console/milestones{/number}", + "notifications_url": "https://api.github.com/repos/jraska/Console/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/jraska/Console/labels{/name}", + "releases_url": "https://api.github.com/repos/jraska/Console/releases{/id}", + "deployments_url": "https://api.github.com/repos/jraska/Console/deployments", + "created_at": "2015-11-15T17:28:22Z", + "updated_at": "2021-01-14T12:40:01Z", + "pushed_at": "2020-04-03T21:15:51Z", + "git_url": "git://github.com/jraska/Console.git", + "ssh_url": "git@github.com:jraska/Console.git", + "clone_url": "https://github.com/jraska/Console.git", + "svn_url": "https://github.com/jraska/Console", + "homepage": null, + "size": 526, + "stargazers_count": 107, + "watchers_count": 107, + "language": "Kotlin", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 12, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 2, + "license": { + "key": "apache-2.0", + "name": "Apache License 2.0", + "spdx_id": "Apache-2.0", + "url": "https://api.github.com/licenses/apache-2.0", + "node_id": "MDc6TGljZW5zZTI=" + }, + "forks": 12, + "open_issues": 2, + "watchers": 107, + "default_branch": "master" + }, + { + "id": 245893320, + "node_id": "MDEwOlJlcG9zaXRvcnkyNDU4OTMzMjA=", + "name": "Dagger-Codelab", + "full_name": "jraska/Dagger-Codelab", + "private": false, + "owner": { + "login": "jraska", + "id": 6277721, + "node_id": "MDQ6VXNlcjYyNzc3MjE=", + "avatar_url": "https://avatars.githubusercontent.com/u/6277721?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/jraska", + "html_url": "https://github.com/jraska", + "followers_url": "https://api.github.com/users/jraska/followers", + "following_url": "https://api.github.com/users/jraska/following{/other_user}", + "gists_url": "https://api.github.com/users/jraska/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jraska/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jraska/subscriptions", + "organizations_url": "https://api.github.com/users/jraska/orgs", + "repos_url": "https://api.github.com/users/jraska/repos", + "events_url": "https://api.github.com/users/jraska/events{/privacy}", + "received_events_url": "https://api.github.com/users/jraska/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/jraska/Dagger-Codelab", + "description": "Codelab to teach and demonstrate usage of dependency injection with Dagger 2", + "fork": false, + "url": "https://api.github.com/repos/jraska/Dagger-Codelab", + "forks_url": "https://api.github.com/repos/jraska/Dagger-Codelab/forks", + "keys_url": "https://api.github.com/repos/jraska/Dagger-Codelab/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/jraska/Dagger-Codelab/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/jraska/Dagger-Codelab/teams", + "hooks_url": "https://api.github.com/repos/jraska/Dagger-Codelab/hooks", + "issue_events_url": "https://api.github.com/repos/jraska/Dagger-Codelab/issues/events{/number}", + "events_url": "https://api.github.com/repos/jraska/Dagger-Codelab/events", + "assignees_url": "https://api.github.com/repos/jraska/Dagger-Codelab/assignees{/user}", + "branches_url": "https://api.github.com/repos/jraska/Dagger-Codelab/branches{/branch}", + "tags_url": "https://api.github.com/repos/jraska/Dagger-Codelab/tags", + "blobs_url": "https://api.github.com/repos/jraska/Dagger-Codelab/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/jraska/Dagger-Codelab/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/jraska/Dagger-Codelab/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/jraska/Dagger-Codelab/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/jraska/Dagger-Codelab/statuses/{sha}", + "languages_url": "https://api.github.com/repos/jraska/Dagger-Codelab/languages", + "stargazers_url": "https://api.github.com/repos/jraska/Dagger-Codelab/stargazers", + "contributors_url": "https://api.github.com/repos/jraska/Dagger-Codelab/contributors", + "subscribers_url": "https://api.github.com/repos/jraska/Dagger-Codelab/subscribers", + "subscription_url": "https://api.github.com/repos/jraska/Dagger-Codelab/subscription", + "commits_url": "https://api.github.com/repos/jraska/Dagger-Codelab/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/jraska/Dagger-Codelab/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/jraska/Dagger-Codelab/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/jraska/Dagger-Codelab/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/jraska/Dagger-Codelab/contents/{+path}", + "compare_url": "https://api.github.com/repos/jraska/Dagger-Codelab/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/jraska/Dagger-Codelab/merges", + "archive_url": "https://api.github.com/repos/jraska/Dagger-Codelab/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/jraska/Dagger-Codelab/downloads", + "issues_url": "https://api.github.com/repos/jraska/Dagger-Codelab/issues{/number}", + "pulls_url": "https://api.github.com/repos/jraska/Dagger-Codelab/pulls{/number}", + "milestones_url": "https://api.github.com/repos/jraska/Dagger-Codelab/milestones{/number}", + "notifications_url": "https://api.github.com/repos/jraska/Dagger-Codelab/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/jraska/Dagger-Codelab/labels{/name}", + "releases_url": "https://api.github.com/repos/jraska/Dagger-Codelab/releases{/id}", + "deployments_url": "https://api.github.com/repos/jraska/Dagger-Codelab/deployments", + "created_at": "2020-03-08T21:49:50Z", + "updated_at": "2021-01-14T18:47:51Z", + "pushed_at": "2021-01-14T21:01:49Z", + "git_url": "git://github.com/jraska/Dagger-Codelab.git", + "ssh_url": "git@github.com:jraska/Dagger-Codelab.git", + "clone_url": "https://github.com/jraska/Dagger-Codelab.git", + "svn_url": "https://github.com/jraska/Dagger-Codelab", + "homepage": "", + "size": 334, + "stargazers_count": 8, + "watchers_count": 8, + "language": "Kotlin", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 5, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": { + "key": "apache-2.0", + "name": "Apache License 2.0", + "spdx_id": "Apache-2.0", + "url": "https://api.github.com/licenses/apache-2.0", + "node_id": "MDc6TGljZW5zZTI=" + }, + "forks": 5, + "open_issues": 0, + "watchers": 8, + "default_branch": "master" + }, + { + "id": 15500221, + "node_id": "MDEwOlJlcG9zaXRvcnkxNTUwMDIyMQ==", + "name": "TimerViews", + "full_name": "jraska/TimerViews", + "private": false, + "owner": { + "login": "jraska", + "id": 6277721, + "node_id": "MDQ6VXNlcjYyNzc3MjE=", + "avatar_url": "https://avatars.githubusercontent.com/u/6277721?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/jraska", + "html_url": "https://github.com/jraska", + "followers_url": "https://api.github.com/users/jraska/followers", + "following_url": "https://api.github.com/users/jraska/following{/other_user}", + "gists_url": "https://api.github.com/users/jraska/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jraska/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jraska/subscriptions", + "organizations_url": "https://api.github.com/users/jraska/orgs", + "repos_url": "https://api.github.com/users/jraska/repos", + "events_url": "https://api.github.com/users/jraska/events{/privacy}", + "received_events_url": "https://api.github.com/users/jraska/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/jraska/TimerViews", + "description": "Library to easily display running time in Android apps.", + "fork": false, + "url": "https://api.github.com/repos/jraska/TimerViews", + "forks_url": "https://api.github.com/repos/jraska/TimerViews/forks", + "keys_url": "https://api.github.com/repos/jraska/TimerViews/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/jraska/TimerViews/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/jraska/TimerViews/teams", + "hooks_url": "https://api.github.com/repos/jraska/TimerViews/hooks", + "issue_events_url": "https://api.github.com/repos/jraska/TimerViews/issues/events{/number}", + "events_url": "https://api.github.com/repos/jraska/TimerViews/events", + "assignees_url": "https://api.github.com/repos/jraska/TimerViews/assignees{/user}", + "branches_url": "https://api.github.com/repos/jraska/TimerViews/branches{/branch}", + "tags_url": "https://api.github.com/repos/jraska/TimerViews/tags", + "blobs_url": "https://api.github.com/repos/jraska/TimerViews/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/jraska/TimerViews/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/jraska/TimerViews/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/jraska/TimerViews/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/jraska/TimerViews/statuses/{sha}", + "languages_url": "https://api.github.com/repos/jraska/TimerViews/languages", + "stargazers_url": "https://api.github.com/repos/jraska/TimerViews/stargazers", + "contributors_url": "https://api.github.com/repos/jraska/TimerViews/contributors", + "subscribers_url": "https://api.github.com/repos/jraska/TimerViews/subscribers", + "subscription_url": "https://api.github.com/repos/jraska/TimerViews/subscription", + "commits_url": "https://api.github.com/repos/jraska/TimerViews/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/jraska/TimerViews/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/jraska/TimerViews/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/jraska/TimerViews/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/jraska/TimerViews/contents/{+path}", + "compare_url": "https://api.github.com/repos/jraska/TimerViews/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/jraska/TimerViews/merges", + "archive_url": "https://api.github.com/repos/jraska/TimerViews/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/jraska/TimerViews/downloads", + "issues_url": "https://api.github.com/repos/jraska/TimerViews/issues{/number}", + "pulls_url": "https://api.github.com/repos/jraska/TimerViews/pulls{/number}", + "milestones_url": "https://api.github.com/repos/jraska/TimerViews/milestones{/number}", + "notifications_url": "https://api.github.com/repos/jraska/TimerViews/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/jraska/TimerViews/labels{/name}", + "releases_url": "https://api.github.com/repos/jraska/TimerViews/releases{/id}", + "deployments_url": "https://api.github.com/repos/jraska/TimerViews/deployments", + "created_at": "2013-12-28T22:57:03Z", + "updated_at": "2020-05-04T15:11:03Z", + "pushed_at": "2014-02-19T22:00:44Z", + "git_url": "git://github.com/jraska/TimerViews.git", + "ssh_url": "git@github.com:jraska/TimerViews.git", + "clone_url": "https://github.com/jraska/TimerViews.git", + "svn_url": "https://github.com/jraska/TimerViews", + "homepage": "", + "size": 1148, + "stargazers_count": 2, + "watchers_count": 2, + "language": "Java", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 3, + "mirror_url": null, + "archived": true, + "disabled": false, + "open_issues_count": 0, + "license": { + "key": "apache-2.0", + "name": "Apache License 2.0", + "spdx_id": "Apache-2.0", + "url": "https://api.github.com/licenses/apache-2.0", + "node_id": "MDc6TGljZW5zZTI=" + }, + "forks": 3, + "open_issues": 0, + "watchers": 2, + "default_branch": "master" + } +] From e0b8868e938d7b0fe49e28d6ddb1bb21c2a5452d Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Thu, 8 Apr 2021 23:36:29 +0200 Subject: [PATCH 04/12] Remove Mockito and annotations dependencies --- feature/users/build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/feature/users/build.gradle b/feature/users/build.gradle index aed9ba83..838daf64 100644 --- a/feature/users/build.gradle +++ b/feature/users/build.gradle @@ -46,9 +46,7 @@ dependencies { testImplementation 'com.jraska.livedata:testing-ktx:1.1.2' testImplementation 'junit:junit:4.13.2' testImplementation 'org.assertj:assertj-core:3.19.0' - testImplementation 'org.mockito:mockito-core:3.8.0' testImplementation 'androidx.arch.core:core-testing:2.1.0' - testImplementation 'androidx.annotation:annotation:1.2.0' testImplementation okHttpMockWebServer testImplementation project(':core-testing') kaptTest rootProject.ext.daggerAnnotationProcessor From 7bb908a4dec214c0b013def60c8bf639f40c244d Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Thu, 8 Apr 2021 23:47:56 +0200 Subject: [PATCH 05/12] Some class moves --- .../src/main/java/com/jraska/github/client/FakeModules.kt | 1 + .../src/main/java/com/jraska/github/client/Fakes.kt | 2 +- .../github/client/{ => android}/FakeCoreAndroidModule.kt | 6 ++++-- .../github/client/{ => android}/RecordingNavigator.kt | 2 +- feature/repo/build.gradle | 1 + 5 files changed, 8 insertions(+), 4 deletions(-) rename core-testing/src/main/java/com/jraska/github/client/{ => android}/FakeCoreAndroidModule.kt (83%) rename core-testing/src/main/java/com/jraska/github/client/{ => android}/RecordingNavigator.kt (93%) diff --git a/core-testing/src/main/java/com/jraska/github/client/FakeModules.kt b/core-testing/src/main/java/com/jraska/github/client/FakeModules.kt index b8716c60..71e0f239 100644 --- a/core-testing/src/main/java/com/jraska/github/client/FakeModules.kt +++ b/core-testing/src/main/java/com/jraska/github/client/FakeModules.kt @@ -1,5 +1,6 @@ package com.jraska.github.client +import com.jraska.github.client.android.FakeCoreAndroidModule import com.jraska.github.client.http.FakeHttpModule import dagger.Module diff --git a/core-testing/src/main/java/com/jraska/github/client/Fakes.kt b/core-testing/src/main/java/com/jraska/github/client/Fakes.kt index fd19f521..d037060c 100644 --- a/core-testing/src/main/java/com/jraska/github/client/Fakes.kt +++ b/core-testing/src/main/java/com/jraska/github/client/Fakes.kt @@ -1,7 +1,7 @@ package com.jraska.github.client import com.jraska.github.client.analytics.EventAnalytics -import com.jraska.github.client.logging.CrashReporter +import com.jraska.github.client.android.RecordingNavigator import com.jraska.github.client.rx.AppSchedulers import io.reactivex.schedulers.Schedulers diff --git a/core-testing/src/main/java/com/jraska/github/client/FakeCoreAndroidModule.kt b/core-testing/src/main/java/com/jraska/github/client/android/FakeCoreAndroidModule.kt similarity index 83% rename from core-testing/src/main/java/com/jraska/github/client/FakeCoreAndroidModule.kt rename to core-testing/src/main/java/com/jraska/github/client/android/FakeCoreAndroidModule.kt index b1cd1cd5..ad646e7b 100644 --- a/core-testing/src/main/java/com/jraska/github/client/FakeCoreAndroidModule.kt +++ b/core-testing/src/main/java/com/jraska/github/client/android/FakeCoreAndroidModule.kt @@ -1,10 +1,10 @@ -package com.jraska.github.client +package com.jraska.github.client.android +import com.jraska.github.client.Fakes import com.jraska.github.client.navigation.Navigator import com.jraska.github.client.rx.AppSchedulers import dagger.Module import dagger.Provides -import okhttp3.HttpUrl import javax.inject.Singleton @Module @@ -20,4 +20,6 @@ object FakeCoreAndroidModule { fun provideNavigator(): Navigator { return Fakes.recordingNavigator() } + + } diff --git a/core-testing/src/main/java/com/jraska/github/client/RecordingNavigator.kt b/core-testing/src/main/java/com/jraska/github/client/android/RecordingNavigator.kt similarity index 93% rename from core-testing/src/main/java/com/jraska/github/client/RecordingNavigator.kt rename to core-testing/src/main/java/com/jraska/github/client/android/RecordingNavigator.kt index 81a61300..2dd2d97b 100644 --- a/core-testing/src/main/java/com/jraska/github/client/RecordingNavigator.kt +++ b/core-testing/src/main/java/com/jraska/github/client/android/RecordingNavigator.kt @@ -1,4 +1,4 @@ -package com.jraska.github.client +package com.jraska.github.client.android import com.jraska.github.client.navigation.Navigator import okhttp3.HttpUrl diff --git a/feature/repo/build.gradle b/feature/repo/build.gradle index d771249f..4749f095 100644 --- a/feature/repo/build.gradle +++ b/feature/repo/build.gradle @@ -45,4 +45,5 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.assertj:assertj-core:3.18.1' testImplementation project(':core-testing') + kaptTest rootProject.ext.daggerAnnotationProcessor } From 07d1edfffc59361e84c1ce3b451b1eebe62bb418 Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Thu, 8 Apr 2021 23:48:54 +0200 Subject: [PATCH 06/12] Do not test history size --- .../com/jraska/github/client/users/UserDetailViewModelTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt b/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt index c6c44122..cde2a8f7 100644 --- a/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt +++ b/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt @@ -38,7 +38,6 @@ class UserDetailViewModelTest { val displayUser = viewModel.userDetail("jraska") .test() - .assertHistorySize(2) .value() as UserDetailViewModel.ViewState.DisplayUser assertThat(displayUser.user).usingRecursiveComparison().isEqualTo(testDetail()) From 5c8c8ed57c9cfb990f0b35ce0786cfb1aea7fdbd Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Fri, 9 Apr 2021 00:30:32 +0200 Subject: [PATCH 07/12] Add RepoDetailViewModelTest --- .../client/android/FakeCoreAndroidModule.kt | 8 ++- .../client/android/FakeSnackbarDisplay.kt | 14 ++++++ feature/repo/build.gradle | 2 + .../client/repo/RepoDetailViewModelTest.kt | 50 ++++++++++++++++++- .../client/repo/di/TestRepoComponent.kt | 16 ++++++ .../repo/model/GitHubApiRepoRepositoryTest.kt | 34 +++++++------ .../src/test/resources/response/error.json | 4 ++ 7 files changed, 109 insertions(+), 19 deletions(-) create mode 100644 core-testing/src/main/java/com/jraska/github/client/android/FakeSnackbarDisplay.kt create mode 100644 feature/repo/src/test/java/com/jraska/github/client/repo/di/TestRepoComponent.kt create mode 100644 feature/repo/src/test/resources/response/error.json diff --git a/core-testing/src/main/java/com/jraska/github/client/android/FakeCoreAndroidModule.kt b/core-testing/src/main/java/com/jraska/github/client/android/FakeCoreAndroidModule.kt index ad646e7b..338cd4e5 100644 --- a/core-testing/src/main/java/com/jraska/github/client/android/FakeCoreAndroidModule.kt +++ b/core-testing/src/main/java/com/jraska/github/client/android/FakeCoreAndroidModule.kt @@ -3,6 +3,8 @@ package com.jraska.github.client.android import com.jraska.github.client.Fakes import com.jraska.github.client.navigation.Navigator import com.jraska.github.client.rx.AppSchedulers +import com.jraska.github.client.ui.SnackbarData +import com.jraska.github.client.ui.SnackbarDisplay import dagger.Module import dagger.Provides import javax.inject.Singleton @@ -21,5 +23,9 @@ object FakeCoreAndroidModule { return Fakes.recordingNavigator() } - + @Provides + @Singleton + internal fun provideFakeSnackbarDisplay(): SnackbarDisplay { + return FakeSnackbarDisplay() + } } diff --git a/core-testing/src/main/java/com/jraska/github/client/android/FakeSnackbarDisplay.kt b/core-testing/src/main/java/com/jraska/github/client/android/FakeSnackbarDisplay.kt new file mode 100644 index 00000000..3cb6ab49 --- /dev/null +++ b/core-testing/src/main/java/com/jraska/github/client/android/FakeSnackbarDisplay.kt @@ -0,0 +1,14 @@ +package com.jraska.github.client.android + +import com.jraska.github.client.ui.SnackbarData +import com.jraska.github.client.ui.SnackbarDisplay + +class FakeSnackbarDisplay : SnackbarDisplay { + private val snackbarsInvoked = mutableListOf() + + fun snackbarsInvoked(): List = snackbarsInvoked + + override fun showSnackbar(snackbarData: SnackbarData) { + snackbarsInvoked.add(snackbarData) + } +} diff --git a/feature/repo/build.gradle b/feature/repo/build.gradle index 4749f095..fe7ae1d6 100644 --- a/feature/repo/build.gradle +++ b/feature/repo/build.gradle @@ -45,5 +45,7 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.assertj:assertj-core:3.18.1' testImplementation project(':core-testing') + testImplementation 'androidx.arch.core:core-testing:2.1.0' + testImplementation 'com.jraska.livedata:testing-ktx:1.1.2' kaptTest rootProject.ext.daggerAnnotationProcessor } diff --git a/feature/repo/src/test/java/com/jraska/github/client/repo/RepoDetailViewModelTest.kt b/feature/repo/src/test/java/com/jraska/github/client/repo/RepoDetailViewModelTest.kt index a6fc14ba..6fa2216a 100644 --- a/feature/repo/src/test/java/com/jraska/github/client/repo/RepoDetailViewModelTest.kt +++ b/feature/repo/src/test/java/com/jraska/github/client/repo/RepoDetailViewModelTest.kt @@ -1,5 +1,51 @@ package com.jraska.github.client.repo -import org.junit.Assert.* +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.jraska.github.client.http.enqueue +import com.jraska.github.client.repo.di.DaggerTestRepoComponent +import com.jraska.github.client.repo.di.TestRepoComponent +import com.jraska.github.client.repo.model.GitHubApiRepoRepositoryTest +import com.jraska.livedata.test +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test -class RepoDetailViewModelTest +internal class RepoDetailViewModelTest { + + @get:Rule + val testRule = InstantTaskExecutorRule() + + lateinit var component: TestRepoComponent + lateinit var repoDetailViewModel: RepoDetailViewModel + + @Before + fun setUp() { + component = DaggerTestRepoComponent.create() + repoDetailViewModel = component.repoDetailViewModel() + } + + @Test + fun whenLoad_thenLoadsProperRepoDetail() { + component.mockWebServer().enqueue("response/repo_detail.json") + component.mockWebServer().enqueue("response/repo_pulls.json") + + val showRepo = repoDetailViewModel.repoDetail("jraska/github-client") + .test() + .value() as RepoDetailViewModel.ViewState.ShowRepo + + assertThat(showRepo.repo).usingRecursiveComparison().isEqualTo(GitHubApiRepoRepositoryTest.expectedRepoDetail()) + } + + @Test + fun whenError_thenLoadsErrorState() { + component.mockWebServer().enqueue("response/error.json") + component.mockWebServer().enqueue("response/error.json") + + val state = repoDetailViewModel.repoDetail("jraska/github-client") + .test() + .value() + + assertThat(state).isInstanceOf(RepoDetailViewModel.ViewState.Error::class.java) + } +} diff --git a/feature/repo/src/test/java/com/jraska/github/client/repo/di/TestRepoComponent.kt b/feature/repo/src/test/java/com/jraska/github/client/repo/di/TestRepoComponent.kt new file mode 100644 index 00000000..a720c05d --- /dev/null +++ b/feature/repo/src/test/java/com/jraska/github/client/repo/di/TestRepoComponent.kt @@ -0,0 +1,16 @@ +package com.jraska.github.client.repo.di + +import com.jraska.github.client.FakeModules +import com.jraska.github.client.repo.RepoDetailViewModel +import com.jraska.github.client.repo.RepoModule +import dagger.Component +import okhttp3.mockwebserver.MockWebServer +import javax.inject.Singleton + +@Singleton +@Component(modules = [RepoModule::class, FakeModules::class]) +internal interface TestRepoComponent { + fun repoDetailViewModel(): RepoDetailViewModel + + fun mockWebServer(): MockWebServer +} diff --git a/feature/repo/src/test/java/com/jraska/github/client/repo/model/GitHubApiRepoRepositoryTest.kt b/feature/repo/src/test/java/com/jraska/github/client/repo/model/GitHubApiRepoRepositoryTest.kt index 4e3c144d..3fb1a6a6 100644 --- a/feature/repo/src/test/java/com/jraska/github/client/repo/model/GitHubApiRepoRepositoryTest.kt +++ b/feature/repo/src/test/java/com/jraska/github/client/repo/model/GitHubApiRepoRepositoryTest.kt @@ -8,7 +8,7 @@ import org.junit.Rule import org.junit.Test import org.threeten.bp.Instant -class GitHubApiRepoRepositoryTest { +internal class GitHubApiRepoRepositoryTest { @get:Rule val mockWebServer = MockWebServer() @@ -30,22 +30,24 @@ class GitHubApiRepoRepositoryTest { private fun repoGitHubApi() = HttpTest.retrofit(mockWebServer.url("/")).create(RepoGitHubApi::class.java) - private fun expectedRepoDetail(): RepoDetail { - return RepoDetail( - RepoHeader( - "jraska", - "github-client", - "Experimental architecture app with example usage intended to be a showcase, test and skeleton app.", - 102, - 14 - ), - RepoDetail.Data(Instant.parse("2016-03-01T23:38:14Z"), 4, "Kotlin", 3), - RepoDetail.PullRequestsState.PullRequests( - listOf( - RepoDetail.PullRequest("Bump epoxy from 4.4.3 to 4.4.4"), - RepoDetail.PullRequest("Bump kotlin_version from 1.4.31 to 1.4.32") + companion object { + fun expectedRepoDetail(): RepoDetail { + return RepoDetail( + RepoHeader( + "jraska", + "github-client", + "Experimental architecture app with example usage intended to be a showcase, test and skeleton app.", + 102, + 14 + ), + RepoDetail.Data(Instant.parse("2016-03-01T23:38:14Z"), 4, "Kotlin", 3), + RepoDetail.PullRequestsState.PullRequests( + listOf( + RepoDetail.PullRequest("Bump epoxy from 4.4.3 to 4.4.4"), + RepoDetail.PullRequest("Bump kotlin_version from 1.4.31 to 1.4.32") + ) ) ) - ) + } } } diff --git a/feature/repo/src/test/resources/response/error.json b/feature/repo/src/test/resources/response/error.json new file mode 100644 index 00000000..3a24768f --- /dev/null +++ b/feature/repo/src/test/resources/response/error.json @@ -0,0 +1,4 @@ +{ + "message": "Not Found", + "documentation_url": "https://docs.github.com/rest" +} From ec813b3ad92fe2ff02e5cf52906cc58f4b45b333 Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Fri, 9 Apr 2021 00:37:44 +0200 Subject: [PATCH 08/12] Added assertion on Snackbar being triggered --- .../client/android/FakeCoreAndroidModule.kt | 5 ++++- .../github/client/repo/RepoDetailViewModelTest.kt | 15 +++++++++++---- .../github/client/repo/di/TestRepoComponent.kt | 5 ++++- .../client/users/UserDetailViewModelTest.kt | 2 +- .../github/client/users/UsersViewModelTest.kt | 4 ++-- .../github/client/users/di/TestUsersComponent.kt | 2 +- 6 files changed, 23 insertions(+), 10 deletions(-) diff --git a/core-testing/src/main/java/com/jraska/github/client/android/FakeCoreAndroidModule.kt b/core-testing/src/main/java/com/jraska/github/client/android/FakeCoreAndroidModule.kt index 338cd4e5..151d9b24 100644 --- a/core-testing/src/main/java/com/jraska/github/client/android/FakeCoreAndroidModule.kt +++ b/core-testing/src/main/java/com/jraska/github/client/android/FakeCoreAndroidModule.kt @@ -25,7 +25,10 @@ object FakeCoreAndroidModule { @Provides @Singleton - internal fun provideFakeSnackbarDisplay(): SnackbarDisplay { + internal fun provideFakeSnackbarDisplay(): FakeSnackbarDisplay { return FakeSnackbarDisplay() } + + @Provides + internal fun provideSnackbarDisplay(fake: FakeSnackbarDisplay): SnackbarDisplay = fake } diff --git a/feature/repo/src/test/java/com/jraska/github/client/repo/RepoDetailViewModelTest.kt b/feature/repo/src/test/java/com/jraska/github/client/repo/RepoDetailViewModelTest.kt index 6fa2216a..040de69a 100644 --- a/feature/repo/src/test/java/com/jraska/github/client/repo/RepoDetailViewModelTest.kt +++ b/feature/repo/src/test/java/com/jraska/github/client/repo/RepoDetailViewModelTest.kt @@ -27,8 +27,8 @@ internal class RepoDetailViewModelTest { @Test fun whenLoad_thenLoadsProperRepoDetail() { - component.mockWebServer().enqueue("response/repo_detail.json") - component.mockWebServer().enqueue("response/repo_pulls.json") + component.mockWebServer.enqueue("response/repo_detail.json") + component.mockWebServer.enqueue("response/repo_pulls.json") val showRepo = repoDetailViewModel.repoDetail("jraska/github-client") .test() @@ -37,10 +37,17 @@ internal class RepoDetailViewModelTest { assertThat(showRepo.repo).usingRecursiveComparison().isEqualTo(GitHubApiRepoRepositoryTest.expectedRepoDetail()) } + @Test + fun whenClicks_thenOpensGitHub() { + repoDetailViewModel.onGitHubIconClicked("jraska/github-client") + + assertThat(component.fakeSnackbarDisplay.snackbarsInvoked().last().text).isEqualTo(R.string.repo_detail_open_web_text) + } + @Test fun whenError_thenLoadsErrorState() { - component.mockWebServer().enqueue("response/error.json") - component.mockWebServer().enqueue("response/error.json") + component.mockWebServer.enqueue("response/error.json") + component.mockWebServer.enqueue("response/error.json") val state = repoDetailViewModel.repoDetail("jraska/github-client") .test() diff --git a/feature/repo/src/test/java/com/jraska/github/client/repo/di/TestRepoComponent.kt b/feature/repo/src/test/java/com/jraska/github/client/repo/di/TestRepoComponent.kt index a720c05d..b0ae77b2 100644 --- a/feature/repo/src/test/java/com/jraska/github/client/repo/di/TestRepoComponent.kt +++ b/feature/repo/src/test/java/com/jraska/github/client/repo/di/TestRepoComponent.kt @@ -1,6 +1,7 @@ package com.jraska.github.client.repo.di import com.jraska.github.client.FakeModules +import com.jraska.github.client.android.FakeSnackbarDisplay import com.jraska.github.client.repo.RepoDetailViewModel import com.jraska.github.client.repo.RepoModule import dagger.Component @@ -12,5 +13,7 @@ import javax.inject.Singleton internal interface TestRepoComponent { fun repoDetailViewModel(): RepoDetailViewModel - fun mockWebServer(): MockWebServer + val mockWebServer: MockWebServer + + val fakeSnackbarDisplay: FakeSnackbarDisplay } diff --git a/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt b/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt index cde2a8f7..0cefcb01 100644 --- a/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt +++ b/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt @@ -28,7 +28,7 @@ class UserDetailViewModelTest { fun before() { val component = DaggerTestUsersComponent.create() viewModel = component.userDetailViewModel() - mockWebServer = component.mockWebServer() + mockWebServer = component.mockWebServer } @Test diff --git a/feature/users/src/test/java/com/jraska/github/client/users/UsersViewModelTest.kt b/feature/users/src/test/java/com/jraska/github/client/users/UsersViewModelTest.kt index 41103a38..73fa80f2 100644 --- a/feature/users/src/test/java/com/jraska/github/client/users/UsersViewModelTest.kt +++ b/feature/users/src/test/java/com/jraska/github/client/users/UsersViewModelTest.kt @@ -18,8 +18,8 @@ class UsersViewModelTest { val component = DaggerTestUsersComponent.create() viewModel = component.usersViewModel() - component.mockWebServer().enqueue("response/users.json") - component.mockWebServer().enqueue("response/users_with_extra.json") + component.mockWebServer.enqueue("response/users.json") + component.mockWebServer.enqueue("response/users_with_extra.json") } @Test diff --git a/feature/users/src/test/java/com/jraska/github/client/users/di/TestUsersComponent.kt b/feature/users/src/test/java/com/jraska/github/client/users/di/TestUsersComponent.kt index f4449b7d..51d7181b 100644 --- a/feature/users/src/test/java/com/jraska/github/client/users/di/TestUsersComponent.kt +++ b/feature/users/src/test/java/com/jraska/github/client/users/di/TestUsersComponent.kt @@ -17,5 +17,5 @@ internal interface TestUsersComponent { fun userDetailViewModel(): UserDetailViewModel - fun mockWebServer(): MockWebServer + val mockWebServer: MockWebServer } From 73f0bebd10e13b430b1fd56b467507801fff8da3 Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Fri, 9 Apr 2021 00:48:06 +0200 Subject: [PATCH 09/12] Add a dummy error handler for error cases --- .../com/jraska/github/client/android/FakeCoreAndroidModule.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core-testing/src/main/java/com/jraska/github/client/android/FakeCoreAndroidModule.kt b/core-testing/src/main/java/com/jraska/github/client/android/FakeCoreAndroidModule.kt index 151d9b24..a37d947d 100644 --- a/core-testing/src/main/java/com/jraska/github/client/android/FakeCoreAndroidModule.kt +++ b/core-testing/src/main/java/com/jraska/github/client/android/FakeCoreAndroidModule.kt @@ -7,6 +7,7 @@ import com.jraska.github.client.ui.SnackbarData import com.jraska.github.client.ui.SnackbarDisplay import dagger.Module import dagger.Provides +import io.reactivex.plugins.RxJavaPlugins import javax.inject.Singleton @Module @@ -14,6 +15,8 @@ object FakeCoreAndroidModule { @Provides @Singleton fun schedulers(): AppSchedulers { + RxJavaPlugins.setErrorHandler { /* empty for now */ } // TODO: 09/04/2021 Better test implementation https://github.com/jraska/github-client/pull/467/checks?check_run_id=2301305103 + return Fakes.trampoline() } From 706b7aa1b62e6bb438624ae6058359c123bc4137 Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Fri, 9 Apr 2021 00:58:27 +0200 Subject: [PATCH 10/12] Add awaiting value to handle the concurrent requests --- .../jraska/github/client/users/UserDetailViewModelTest.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt b/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt index 0cefcb01..6546cfcb 100644 --- a/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt +++ b/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt @@ -38,6 +38,7 @@ class UserDetailViewModelTest { val displayUser = viewModel.userDetail("jraska") .test() + .awaitValue() .value() as UserDetailViewModel.ViewState.DisplayUser assertThat(displayUser.user).usingRecursiveComparison().isEqualTo(testDetail()) @@ -48,7 +49,7 @@ class UserDetailViewModelTest { mockWebServer.enqueue("response/error.json") mockWebServer.enqueue("response/error.json") - val value = viewModel.userDetail("jraska").test().value() + val value = viewModel.userDetail("jraska").test().awaitValue().value() assertThat(value).isInstanceOf(UserDetailViewModel.ViewState.Error::class.java) } @@ -61,7 +62,7 @@ class UserDetailViewModelTest { mockWebServer.onUrlReturn(".*/users/jraska".toRegex(), "response/jraska.json") mockWebServer.onUrlPartReturn("users/jraska/repos", "response/jraska_repos.json") - viewModel.userDetail("mojombo").test() + viewModel.userDetail("mojombo").test().awaitValue() viewModel.userDetail("mojombo").test() assertThat(mockWebServer.requestCount).isEqualTo(2) From c8bdc7e8a16eebcb2d5b4c01139da1170e357840 Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Fri, 9 Apr 2021 01:12:23 +0200 Subject: [PATCH 11/12] Do a small haack to make sure the loading state doesn't break things --- .../jraska/github/client/users/UserDetailViewModelTest.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt b/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt index 6546cfcb..195fdc9b 100644 --- a/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt +++ b/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt @@ -36,9 +36,12 @@ class UserDetailViewModelTest { mockWebServer.onUrlReturn(".*/users/jraska".toRegex(), "response/jraska.json") mockWebServer.onUrlPartReturn("users/jraska/repos", "response/jraska_repos.json") - val displayUser = viewModel.userDetail("jraska") + val testObserver = viewModel.userDetail("jraska") .test() - .awaitValue() + + val displayUser = testObserver + .doOnChanged { if(it is UserDetailViewModel.ViewState.Loading) testObserver.awaitNextValue() } + .apply { println(valueHistory()) } .value() as UserDetailViewModel.ViewState.DisplayUser assertThat(displayUser.user).usingRecursiveComparison().isEqualTo(testDetail()) From 6f8b8d589bd01503efbd9b7a91570e8ea59c02cb Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Fri, 9 Apr 2021 01:41:51 +0200 Subject: [PATCH 12/12] Use properly injected AppSchedulers --- .../java/com/jraska/github/client/users/UsersModule.kt | 5 +++-- .../client/users/model/GitHubApiUsersRepository.kt | 4 +++- .../github/client/users/UserDetailViewModelTest.kt | 10 +++------- .../client/users/model/GitHubApiUsersRepositoryTest.kt | 3 ++- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/feature/users/src/main/java/com/jraska/github/client/users/UsersModule.kt b/feature/users/src/main/java/com/jraska/github/client/users/UsersModule.kt index f69d102b..7037a796 100644 --- a/feature/users/src/main/java/com/jraska/github/client/users/UsersModule.kt +++ b/feature/users/src/main/java/com/jraska/github/client/users/UsersModule.kt @@ -6,6 +6,7 @@ import com.jraska.github.client.config.MutableConfigDef import com.jraska.github.client.config.MutableConfigSetup import com.jraska.github.client.config.MutableConfigType import com.jraska.github.client.core.android.LinkLauncher +import com.jraska.github.client.rx.AppSchedulers import com.jraska.github.client.users.model.GitHubApiUsersRepository import com.jraska.github.client.users.model.GitHubUsersApi import com.jraska.github.client.users.model.UsersRepository @@ -24,10 +25,10 @@ object UsersModule { @Provides @Singleton - internal fun provideUsersRepository(retrofit: Retrofit): UsersRepository { + internal fun provideUsersRepository(retrofit: Retrofit, appSchedulers: AppSchedulers): UsersRepository { val usersApi = retrofit.create(GitHubUsersApi::class.java) - return GitHubApiUsersRepository(usersApi) + return GitHubApiUsersRepository(usersApi, appSchedulers) } @Provides diff --git a/feature/users/src/main/java/com/jraska/github/client/users/model/GitHubApiUsersRepository.kt b/feature/users/src/main/java/com/jraska/github/client/users/model/GitHubApiUsersRepository.kt index 2249eea1..7c467489 100644 --- a/feature/users/src/main/java/com/jraska/github/client/users/model/GitHubApiUsersRepository.kt +++ b/feature/users/src/main/java/com/jraska/github/client/users/model/GitHubApiUsersRepository.kt @@ -1,5 +1,6 @@ package com.jraska.github.client.users.model +import com.jraska.github.client.rx.AppSchedulers import io.reactivex.Observable import io.reactivex.Single import io.reactivex.schedulers.Schedulers @@ -7,6 +8,7 @@ import java.util.Collections internal class GitHubApiUsersRepository( private val gitHubUsersApi: GitHubUsersApi, + private val appSchedulers: AppSchedulers ) : UsersRepository { private val converter: UserDetailWithReposConverter = UserDetailWithReposConverter.INSTANCE @@ -21,7 +23,7 @@ internal class GitHubApiUsersRepository( override fun getUserDetail(login: String, reposInSection: Int): Observable { return gitHubUsersApi.getUserDetail(login) - .subscribeOn(Schedulers.io()) // this has to be here now to run requests in parallel + .subscribeOn(appSchedulers.io) // this has to be here now to run requests in parallel .zipWith(gitHubUsersApi.getRepos(login), { a: GitHubUserDetail, b: List -> Pair(a, b) }) .map { result -> converter.translateUserDetail(result.component1(), result.component2(), reposInSection) } .toObservable() diff --git a/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt b/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt index 195fdc9b..0cefcb01 100644 --- a/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt +++ b/feature/users/src/test/java/com/jraska/github/client/users/UserDetailViewModelTest.kt @@ -36,12 +36,8 @@ class UserDetailViewModelTest { mockWebServer.onUrlReturn(".*/users/jraska".toRegex(), "response/jraska.json") mockWebServer.onUrlPartReturn("users/jraska/repos", "response/jraska_repos.json") - val testObserver = viewModel.userDetail("jraska") + val displayUser = viewModel.userDetail("jraska") .test() - - val displayUser = testObserver - .doOnChanged { if(it is UserDetailViewModel.ViewState.Loading) testObserver.awaitNextValue() } - .apply { println(valueHistory()) } .value() as UserDetailViewModel.ViewState.DisplayUser assertThat(displayUser.user).usingRecursiveComparison().isEqualTo(testDetail()) @@ -52,7 +48,7 @@ class UserDetailViewModelTest { mockWebServer.enqueue("response/error.json") mockWebServer.enqueue("response/error.json") - val value = viewModel.userDetail("jraska").test().awaitValue().value() + val value = viewModel.userDetail("jraska").test().value() assertThat(value).isInstanceOf(UserDetailViewModel.ViewState.Error::class.java) } @@ -65,7 +61,7 @@ class UserDetailViewModelTest { mockWebServer.onUrlReturn(".*/users/jraska".toRegex(), "response/jraska.json") mockWebServer.onUrlPartReturn("users/jraska/repos", "response/jraska_repos.json") - viewModel.userDetail("mojombo").test().awaitValue() + viewModel.userDetail("mojombo").test() viewModel.userDetail("mojombo").test() assertThat(mockWebServer.requestCount).isEqualTo(2) diff --git a/feature/users/src/test/java/com/jraska/github/client/users/model/GitHubApiUsersRepositoryTest.kt b/feature/users/src/test/java/com/jraska/github/client/users/model/GitHubApiUsersRepositoryTest.kt index 9390e0a7..370482ec 100644 --- a/feature/users/src/test/java/com/jraska/github/client/users/model/GitHubApiUsersRepositoryTest.kt +++ b/feature/users/src/test/java/com/jraska/github/client/users/model/GitHubApiUsersRepositoryTest.kt @@ -1,5 +1,6 @@ package com.jraska.github.client.users.model +import com.jraska.github.client.Fakes import com.jraska.github.client.http.HttpTest import com.jraska.github.client.http.enqueue import com.jraska.github.client.http.onUrlPartReturn @@ -16,7 +17,7 @@ class GitHubApiUsersRepositoryTest { @Before fun setUp() { - repository = GitHubApiUsersRepository(HttpTest.retrofit(mockWebServer.url("/")).create(GitHubUsersApi::class.java)) + repository = GitHubApiUsersRepository(HttpTest.retrofit(mockWebServer.url("/")).create(GitHubUsersApi::class.java), Fakes.trampoline()) } @Test