8000 Working instrumented test with Activity by jraska · Pull Request #504 · jraska/github-client · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Working instrumented test with Activity #504

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

Merged
merged 6 commits into from
Apr 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ android {
srcDirs += ["../feature/about/src/androidTest/java"]
srcDirs += ["../feature/users/src/androidTest/java"]
}
sourceSets.androidTest.assets {
srcDirs += ["../feature/about/src/androidTest/assets"]
srcDirs += ["../feature/users/src/androidTest/assets"]
}
}
}

Expand Down Expand Up @@ -157,10 +161,12 @@ dependencies {
androidTestImplementation 'com.airbnb.okreplay:espresso:1.6.0'
androidTestImplementation 'com.squareup.rx.idler:rx3-idler:0.11.0'
androidTestImplementation project(':core-testing')
androidTestImplementation project(':core-android-testing')

androidTestImplementation rootProject.ext.retrofit
androidTestImplementation rootProject.ext.retrofitGsonConverter
androidTestImplementation rootProject.ext.retrofitRxJavaAdapter
androidTestImplementation okHttpMockWebServer

kaptAndroidTest rootProject.ext.daggerAnnotationProcessor
}
Expand Down
13 changes: 11 additions & 2 deletions app/src/androidTest/java/com/jraska/github/client/TestUITestApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import androidx.test.platform.app.InstrumentationRegistry
import com.jraska.github.client.core.android.BaseApp
import com.jraska.github.client.core.android.ServiceModel
import com.jraska.github.client.http.ReplayHttpModule
import com.jraska.github.client.users.test.DeepLinkRecordingComponent
import com.jraska.github.client.users.test.FakeDeepLinkRecordingModule
import dagger.BindsInstance
import dagger.Component
import javax.inject.Singleton
Expand All @@ -30,8 +32,15 @@ class TestUITestApp : BaseApp() {
}

@Singleton
@Component(modules = [SharedModules::class, FakeCoreModule::class, ReplayHttpModule::class])
interface TestAppComponent : AppComponent {
@Component(
modules = [
SharedModules::class,
FakeCoreModule::class,
ReplayHttpModule::class,
FakeDeepLinkRecordingModule::class
]
)
interface TestAppComponent : AppComponent, DeepLinkRecordingComponent {
val config: FakeConfig

@Component.Factory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import dagger.Module
import dagger.Provides
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import okreplay.AndroidTapeRoot
import okreplay.OkReplayConfig
Expand All @@ -34,7 +33,6 @@ object ReplayHttpModule {
}

private val REPLAY_MEDIATOR = OkReplayMediator()
private const val NETWORK_ERROR_MESSAGE = "You are trying to do network requests in tests you naughty developer!"

private fun createRetrofit(): Retrofit {
return Retrofit.Builder()
Expand Down Expand Up @@ -85,12 +83,7 @@ object ReplayHttpModule {

REPLAY_MEDIATOR.configure(builder)

val noNetworkInterceptor = object : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
throw UnsupportedOperationException(NETWORK_ERROR_MESSAGE)
}
}
builder.addNetworkInterceptor(noNetworkInterceptor)
builder.addInterceptor(MockWebServerInterceptor)

return builder.build()
}
Expand Down
7 changes: 4 additions & 3 deletions app/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jraska.github.client">
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jraska.github.client">

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

<application android:networkSecurityConfig="@xml/network_security_config"/>
</manifest>
6 changes: 6 additions & 0 deletions app/src/debug/res/xml/network_security_config.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localhost</domain>
</domain-config>
</network-security-config>
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ interface LinkLauncher {
}

enum class Priority(val value: Int) {
EXACT_MATCH(0),
PATH_LENGTH(1)
TESTING(0),
EXACT_MATCH(1),
PATH_LENGTH(2)
}
}
1 change: 1 addition & 0 deletions core-android-testing/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
35 changes: 35 additions & 0 deletions core-android-testing/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'

android {
compileSdkVersion 30
defaultConfig {
minSdkVersion 24
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}

dependencies {
implementation project(':core-api')
implementation project(':core-android-api')

kapt rootProject.ext.daggerAnnotationProcessor
implementation rootProject.ext.dagger

implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

implementation okHttpMockWebServer
implementation rootProject.ext.okHttp
implementation 'com.squareup.okio:okio:2.10.0'

implementation 'com.squareup.rx.idler:rx3-idler:0.11.0'
}
1 change: 1 addition & 0 deletions core-android-testing/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest package="com.jraska.github.client.android.testing"/>
32 changes: 32 additions & 0 deletions ...-android-testing/src/main/java/com/jraska/github/client/android/test/DeepLinksRecorder.kt < 10000 /clipboard-copy>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.jraska.github.client.android.test

import android.app.Activity
import com.jraska.github.client.core.android.LinkLauncher
import okhttp3.HttpUrl

class DeepLinksRecorder : LinkLauncher {
val linksLaunched = mutableListOf<HttpUrl>()
private var swallowLinks = false

override fun priority() = LinkLauncher.Priority.TESTING

override fun launch(inActivity: Activity, deepLink: HttpUrl): LinkLauncher.Result {
linksLaunched.add(deepLink)

return if (swallowLinks) {
LinkLauncher.Result.LAUNCHED // swallows link only as recorded
} else {
LinkLauncher.Result.NOT_LAUNCHED
}
}

fun usingLinkRecording(block: (DeepLinksRecorder) -> Unit) {
try {
swallowLinks = true
block(this)
} finally {
swallowLinks = false
linksLaunched.clear()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.jraska.github.client.android.test

import android.app.Application
import androidx.lifecycle.ViewModel
import com.jraska.github.client.core.android.OnAppCreate
import com.jraska.github.client.core.android.ServiceModel
import com.squareup.rx3.idler.Rx3Idler
import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import dagger.multibindings.IntoSet
import io.reactivex.rxjava3.plugins.RxJavaPlugins

@Module
object FakeAndroidCoreModule {

@Provides
@IntoMap
@ClassKey(ServiceModel::class)
internal fun provideServiceModel(): ServiceModel {
return object : ServiceModel {} // Make sure the collection is not empty
}

@Provides
@IntoMap
@ClassKey(ViewModel::class)
internal fun provideViewModel(): ViewModel {
return object : ViewModel() {} // Make sure the collection is not empty
}

@Provides
@IntoSet
fun startupRxIdlers() : OnAppCreate {
return object : OnAppCreate {
override fun onCreate(app: Application) {
RxJavaPlugins.setInitComputationSchedulerHandler(
Rx3Idler.create("RxJava 3.x Computation Scheduler")
)
RxJavaPlugins.setInitIoSchedulerHandler(
Rx3Idler.create("RxJava 3.x IO Scheduler")
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.jraska.github.client.users.test

import androidx.test.platform.app.InstrumentationRegistry
import com.jraska.github.client.android.test.DeepLinksRecorder
import com.jraska.github.client.core.android.BaseApp
import com.jraska.github.client.core.android.LinkLauncher
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoSet
import javax.inject.Singleton

@Module
object FakeDeepLinkRecordingModule {

@Provides
@Singleton
fun recordingLauncher() = DeepLinksRecorder()

@Provides
@IntoSet
fun linkLauncher(launcher: DeepLinksRecorder): LinkLauncher = launcher
}

interface DeepLinkRecordingComponent {
val deepLinksRecorder: DeepLinksRecorder
}

fun usingLinkRecording(block: (DeepLinksRecorder) -> Unit) {
val appComponent = (InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as BaseApp).appComponent

(appComponent as DeepLinkRecordingComponent).deepLinksRecorder.usingLinkRecording(block)
}
1 change: 1 addition & 0 deletions core-testing/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ dependencies {
kapt rootProject.ext.daggerAnnotationProcessor
implementation rootProject.ext.dagger

implementation 'junit:junit:4.13.2'
implementation 'io.reactivex.rxjava3:rxjava:3.0.12'

implementation rootProject.ext.retrofit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,8 @@ 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("/"))
fun provideRetrofit() = HttpTest.retrofit()
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
package com.jraska.github.client.http

import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.rules.ExternalResource
import retrofit2.Retrofit
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.io.File

object HttpTest {
fun retrofit(baseUrl: HttpUrl): Retrofit {
private val DEFAULT_BASE_URL = "https://api.github.com".toHttpUrl()

fun retrofit(baseUrl: HttpUrl = DEFAULT_BASE_URL): Retrofit {
return Retrofit.Builder()
.baseUrl(baseUrl)
.client(OkHttpClient.Builder().addInterceptor(HttpLoggingInterceptor { println(it) }.apply {
level = HttpLoggingInterceptor.Level.BASIC
}).build())
.client(
OkHttpClient.Builder()
.also { if (baseUrl == DEFAULT_BASE_URL) it.addInterceptor(MockWebServerInterceptor) } // TODO: 30/4/21 Hack - unification of network mocking needed
.addInterceptor(HttpLoggingInterceptor { println(it) }.apply {
level = HttpLoggingInterceptor.Level.BASIC
}).build()
)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.createSynchronous())
.build()
Expand Down Expand Up @@ -51,3 +61,24 @@ internal fun json(path: String): String {
return String(file.readBytes())
}

object MockWebServerInterceptor : Interceptor {
var mockWebServer: MockWebServer? = null

override fun intercept(chain: Interceptor.Chain): Response {
val webServer = mockWebServer ?: throw UnsupportedOperationException("You are trying to do network requests in tests you naughty developer!")

val newRequest = chain.request().newBuilder().url(webServer.url(chain.request().url.encodedPath)).build()
return chain.proceed(newRequest)
}
}

class MockWebServerInterceptorRule(private val mockWebServer: MockWebServer) : ExternalResource() {
override fun before() {
MockWebServerInterceptor.mockWebServer = mockWebServer
}

override fun after() {
MockWebServerInterceptor.mockWebServer = null
}
}

Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.jraska.github.client.repo

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.jraska.github.client.http.MockWebServerInterceptorRule
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 okhttp3.mockwebserver.MockWebServer
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
Expand All @@ -16,6 +18,10 @@ internal class RepoDetailViewModelTest {
@get:Rule
val testRule = InstantTaskExecutorRule()

@get:Rule val mockWebServer = MockWebServer()

@get:Rule val mockWebServerInterceptorRule = MockWebServerInterceptorRule(mockWebServer)

lateinit var component: TestRepoComponent
lateinit var repoDetailViewModel: RepoDetailViewModel

Expand All @@ -27,8 +33,8 @@ internal class RepoDetailViewModelTest {

@Test
fun whenLoadThenLoadsProperRepoDetail() {
component.mockWebServer.enqueue("response/repo_detail.json")
component.mockWebServer.enqueue("response/repo_pulls.json")
mockWebServer.enqueue("response/repo_detail.json")
mockWebServer.enqueue("response/repo_pulls.json")

val showRepo = repoDetailViewModel.repoDetail("jraska/github-client")
.test()
Expand All @@ -46,8 +52,8 @@ internal class RepoDetailViewModelTest {

@Test
fun whenErrorThenLoadsErrorState() {
component.mockWebServer.enqueue("response/error.json")
component.mockWebServer.enqueue("response/error.json")
mockWebServer.enqueue("response/error.json")
mockWebServer.enqueue("response/error.json")

val state = repoDetailViewModel.repoDetail("jraska/github-client")
.test()
Expand Down
Loading
0