Issue#41 Copy full example into separate module with Hilt Integration

This commit is contained in:
Gergely Hegedus 2022-09-27 17:16:05 +03:00
parent 69e76dc0da
commit 52a99a82fc
229 changed files with 8416 additions and 11 deletions

View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,43 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdk 31
defaultConfig {
minSdk 21
targetSdk 31
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
buildConfig = false
}
}
// since it itself contains the TestUtil it doesn't have tests of it's own
disableTestTasks(this)
dependencies {
implementation project(":hilt:hilt-network")
implementation "com.google.dagger:hilt-android-testing:$hilt_version"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation project(':mockserver')
implementation "androidx.test.espresso:espresso-core:$espresso_version"
}

View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.fnives.test.showcase.hilt.network.testutil">
</manifest>

View file

@ -0,0 +1,38 @@
package org.fnives.test.showcase.hilt.network.testutil
import okhttp3.tls.HandshakeCertificates
import org.fnives.test.showcase.hilt.network.di.HiltNetworkModule
import org.fnives.test.showcase.hilt.network.shared.PlatformInterceptor
import org.fnives.test.showcase.network.mockserver.MockServerScenarioSetup
// @Module
// @TestInstallIn(
// components = [SingletonComponent::class],
// replaces = [BindsBaseOkHttpClient::class]
// )
object HttpsConfigurationModuleTemplate {
lateinit var handshakeCertificates: HandshakeCertificates
// @Provides
// @Singleton
// @SessionLessQualifier
fun bindsBaseOkHttpClient(enableLogging: Boolean, platformInterceptor: PlatformInterceptor) =
HiltNetworkModule.provideSessionLessOkHttpClient(enableLogging, platformInterceptor)
.newBuilder()
.sslSocketFactory(
handshakeCertificates.sslSocketFactory(),
handshakeCertificates.trustManager
)
.build()
fun startWithHTTPSMockWebServer(): Pair<MockServerScenarioSetup, String> {
val mockServerScenarioSetup = MockServerScenarioSetup()
val url = mockServerScenarioSetup.start(true)
handshakeCertificates = mockServerScenarioSetup.clientCertificates
?: throw IllegalStateException("ClientCertificate should be accessable")
return mockServerScenarioSetup to url
}
}

View file

@ -0,0 +1,37 @@
package org.fnives.test.showcase.hilt.network.testutil
import androidx.annotation.CheckResult
import androidx.test.espresso.IdlingResource
import okhttp3.OkHttpClient
import org.fnives.test.showcase.hilt.network.di.SessionLessQualifier
import org.fnives.test.showcase.hilt.network.di.SessionQualifier
import javax.inject.Inject
class NetworkSynchronization @Inject constructor(
@SessionQualifier
private val sessionOkhttpClient: OkHttpClient,
@SessionLessQualifier
private val sessionlessOkhttpClient: OkHttpClient
) {
@CheckResult
fun networkIdlingResources(): List<IdlingResource> =
OkHttpClientTypes.values()
.map { it to getOkHttpClient(it) }
.associateBy { it.second.dispatcher }
.values
.map { (key, client) -> client.asIdlingResource(key.qualifier) }
private fun getOkHttpClient(type: OkHttpClientTypes): OkHttpClient =
when (type) {
OkHttpClientTypes.SESSION -> sessionOkhttpClient
OkHttpClientTypes.SESSIONLESS -> sessionlessOkhttpClient
}
private fun OkHttpClient.asIdlingResource(name: String): IdlingResource =
OkHttp3IdlingResource.create(name, this)
enum class OkHttpClientTypes(val qualifier: String) {
SESSION("SESSION-NETWORKING"), SESSIONLESS("SESSIONLESS-NETWORKING")
}
}

View file

@ -0,0 +1,76 @@
package org.fnives.test.showcase.hilt.network.testutil
import androidx.annotation.CheckResult
import androidx.annotation.NonNull
import androidx.test.espresso.IdlingResource
import okhttp3.Dispatcher
import okhttp3.OkHttpClient
/**
* AndroidX version of Jake Wharton's OkHttp3IdlingResource.
*
* Reference: https://github.com/JakeWharton/okhttp-idling-resource/blob/master/src/main/java/com/jakewharton/espresso/OkHttp3IdlingResource.java
*/
class OkHttp3IdlingResource private constructor(
private val name: String,
private val dispatcher: Dispatcher
) : IdlingResource {
@Volatile
var callback: IdlingResource.ResourceCallback? = null
private var isIdleCallbackWasCalled: Boolean = true
init {
val currentCallback = dispatcher.idleCallback
dispatcher.idleCallback = Runnable {
sleepForDispatcherDefaultCallInRetrofitErrorState()
callback?.onTransitionToIdle()
currentCallback?.run()
isIdleCallbackWasCalled = true
}
}
override fun getName(): String = name
override fun isIdleNow(): Boolean {
val isIdle = dispatcher.runningCallsCount() == 0
if (isIdle) {
// sometime the callback is just not properly called it seems, or maybe sync error.
// if it isn't called Espresso crashes, so we add this here.
callback?.onTransitionToIdle()
}
return isIdle
}
override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) {
this.callback = callback
}
companion object {
/**
* Create a new [IdlingResource] from `client` as `name`. You must register
* this instance using `Espresso.registerIdlingResources`.
*/
@CheckResult
@NonNull
fun create(@NonNull name: String?, @NonNull client: OkHttpClient?): OkHttp3IdlingResource {
if (name == null) throw NullPointerException("name == null")
if (client == null) throw NullPointerException("client == null")
return OkHttp3IdlingResource(name, client.dispatcher)
}
/**
* This is required, because in case of Errors Retrofit uses Dispatcher.Default to suspendThrow
* see: retrofit2.KotlinExtensions.kt Exception.suspendAndThrow
* Relevant code issue: https://github.com/square/retrofit/blob/6cd6f7d8287f73909614cb7300fcde05f5719750/retrofit/src/main/java/retrofit2/KotlinExtensions.kt#L121
* This is the current suggested approach to their problem with Unchecked Exceptions
*
* Sadly Dispatcher.Default cannot be replaced yet, so we can't swap it out in tests:
* https://github.com/Kotlin/kotlinx.coroutines/issues/1365
*
* This brings us to this sleep for now.
*/
fun sleepForDispatcherDefaultCallInRetrofitErrorState() {
Thread.sleep(200L)
}
}
}