Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Full Dagger Graph for unit tests #467

Merged
merged 12 commits into from
Apr 8, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
Expand Down
1 change: 1 addition & 0 deletions core-testing/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions core-testing/src/main/java/com/jraska/github/client/FakeModules.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.jraska.github.client

import com.jraska.github.client.android.FakeCoreAndroidModule
import com.jraska.github.client.http.FakeHttpModule
import dagger.Module

@Module(
includes = arrayOf(
FakeCoreModule::class,
FakeCoreAndroidModule::class,
FakeConfigModule::class,
FakeHttpModule::class,
)
)
object FakeModules
6 changes: 5 additions & 1 deletion core-testing/src/main/java/com/jraska/github/client/Fakes.kt
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -29,4 +29,8 @@ object Fakes {
fun recordingAnalyticsProperty(): RecordingAnalyticsProperty {
return RecordingAnalyticsProperty()
}

fun recordingNavigator() : RecordingNavigator {
return RecordingNavigator()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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 io.reactivex.plugins.RxJavaPlugins
import javax.inject.Singleton

@Module
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()
}

@Provides
@Singleton
fun provideNavigator(): Navigator {
return Fakes.recordingNavigator()
}

@Provides
@Singleton
internal fun provideFakeSnackbarDisplay(): FakeSnackbarDisplay {
return FakeSnackbarDisplay()
}

@Provides
internal fun provideSnackbarDisplay(fake: FakeSnackbarDisplay): SnackbarDisplay = fake
}
Original file line number Diff line number Diff line change
@@ -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<SnackbarData>()

fun snackbarsInvoked(): List<SnackbarData> = snackbarsInvoked

override fun showSnackbar(snackbarData: SnackbarData) {
snackbarsInvoked.add(snackbarData)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.jraska.github.client.android

import com.jraska.github.client.navigation.Navigator
import okhttp3.HttpUrl

class RecordingNavigator: Navigator {
val screensStarted: MutableList<Any> = 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")
}
}
Original file line number Diff line number Diff line change
@@ -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("/"))
}
3 changes: 3 additions & 0 deletions feature/repo/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +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
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,58 @@
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 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")

val state = repoDetailViewModel.repoDetail("jraska/github-client")
.test()
.value()

assertThat(state).isInstanceOf(RepoDetailViewModel.ViewState.Error::class.java)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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
import okhttp3.mockwebserver.MockWebServer
import javax.inject.Singleton

@Singleton
@Component(modules = [RepoModule::class, FakeModules::class])
internal interface TestRepoComponent {
fun repoDetailViewModel(): RepoDetailViewModel

val mockWebServer: MockWebServer

val fakeSnackbarDisplay: FakeSnackbarDisplay
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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")
)
)
)
)
}
}
}
4 changes: 4 additions & 0 deletions feature/repo/src/test/resources/response/error.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"message": "Not Found",
"documentation_url": "https://docs.github.com/rest"
}
3 changes: 1 addition & 2 deletions feature/users/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,8 @@ 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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
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
import java.util.Collections

internal class GitHubApiUsersRepository(
private val gitHubUsersApi: GitHubUsersApi,
private val appSchedulers: AppSchedulers
) : UsersRepository {
private val converter: UserDetailWithReposConverter =
UserDetailWithReposConverter.INSTANCE
Expand All @@ -21,7 +23,7 @@ internal class GitHubApiUsersRepository(

override fun getUserDetail(login: String, reposInSection: Int): Observable<UserDetail> {
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<GitHubUserRepo> -> Pair(a, b) })
.map { result -> converter.translateUserDetail(result.component1(), result.component2(), reposInSection) }
.toObservable()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading