Skip to content

Commit

Permalink
Integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
denist2001 committed Sep 16, 2020
1 parent 19279ed commit 72f445d
Show file tree
Hide file tree
Showing 20 changed files with 974 additions and 65 deletions.
31 changes: 28 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ android {
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "com.codechallenge.doctorscatalog.MainTestRunner"
}

buildTypes {
Expand Down Expand Up @@ -44,6 +44,10 @@ android {
}
}

kapt {
correctErrorTypes true
}

dependencies {
// androidx
implementation 'androidx.appcompat:appcompat:1.2.0'
Expand Down Expand Up @@ -71,12 +75,33 @@ dependencies {
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
//image downloading tools
implementation "io.coil-kt:coil:$coil_version"
//correct instantiating of MockWebServer
implementation "com.squareup.okhttp3:logging-interceptor:$mockwebserver_version"

// unit tests
testImplementation 'junit:junit:4.13'

// integration tests
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
androidTestImplementation "androidx.test.espresso:espresso-intents:$espresso_version"
// manipulation with recyclerview
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espresso_version"
// allow to wait conditions
androidTestImplementation 'org.awaitility:awaitility-kotlin:3.1.1'
// allow managing UI device conditions
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
// dependency injection test tool
androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
kaptAndroidTest "com.google.dagger:hilt-android-compiler:$hilt_version"
// mock service for kotlin
androidTestImplementation "org.mockito:mockito-core:$mockito_version"
androidTestImplementation "org.mockito:mockito-android:$mockito_version"
// network mock
androidTestImplementation "com.squareup.okhttp3:mockwebserver:$mockwebserver_version"
// grant permissions rule
androidTestImplementation "androidx.test:rules:$androidx_test_version"
androidTestImplementation "androidx.test:runner:$androidx_test_version"
// navigation test
androidTestImplementation "androidx.navigation:navigation-testing:$navigation_version"
}
2 changes: 1 addition & 1 deletion app/src/androidTest/assets/doctors-CvQD7gEAAKjcb.json
Original file line number Diff line number Diff line change
Expand Up @@ -548,5 +548,5 @@
"translation": null
}
],
"lastKey": "dJiBHLZjYWs6iSqb"
"lastKey": null
}
1 change: 1 addition & 0 deletions app/src/androidTest/assets/empty.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
34 changes: 34 additions & 0 deletions app/src/androidTest/assets/malformed_doctors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"doctors":
{
"id": 123456789,
"name": "Dr. med. Mario Voss",
"photoId": null,
"rating": "2.5",
"address": "Friedrichstraße 115, 10117 Berlin, Germany",
"lat": "52.526779",
"lng": "13.387201",
"highlighted": false,
"reviewCount": 5,
"specialityIds": [
1
],
"source": "google",
"phoneNumber": "+29 30 28391555",
"email": null,
"website": "http://www.vivy-doc.de/",
"openingHours": [
"D1T08:00/D1T12:00",
"D1T15:00/D1T18:00",
"D2T08:00/D2T12:00",
"D3T08:00/D3T12:00",
"D2T08:00/D2T12:00",
"D2T15:00/D2T18:00",
"D5T08:00/D5T12:00"
],
"integration": null,
"translation": null
}
,
"lastKey": "CvQD7gEAAKjcb"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.codechallenge.doctorscatalog

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.rule.ActivityTestRule
import com.codechallenge.doctorscatalog.di.NetworkModule
import com.codechallenge.doctorscatalog.ui.main.ItemViewHolder
import com.codechallenge.doctorscatalog.utils.getStringFrom
import com.codechallenge.doctorscatalog.utils.waitUntilViewWithId
import com.codechallenge.doctorscatalog.utils.waitUntilViewWithText
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.InternalCoroutinesApi
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.RecordedRequest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
@UninstallModules(NetworkModule::class)
@HiltAndroidTest
class ClickOnItems {

@get:Rule
val hiltRule = HiltAndroidRule(this)

@get:Rule
val activityTestRule = ActivityTestRule(MainActivity::class.java, true, false)

private lateinit var mockServer: MockWebServer

@Before
fun setUp() {
hiltRule.inject()
mockServer = MockWebServer()
mockServer.start(8080)
}

@ExperimentalCoroutinesApi
@InternalCoroutinesApi
@Test
fun clickOnVivyDoctors_shouldOpenDetails() {
setDispatcher("doctors.json", 200)
activityTestRule.launchActivity(null)

waitUntilViewWithId(R.id.doctors_list_rv, 5, isDisplayed())
waitUntilViewWithText("Gemeinschaftspraxis Dr. Hintsche und Dr. Klausen", 5, isDisplayed())
onView(withId(R.id.doctors_list_rv)).perform(RecyclerViewActions.actionOnItemAtPosition<ItemViewHolder>(3, click()))
waitUntilViewWithId(R.id.name_tv, 5, isDisplayed())
waitUntilViewWithText("Gemeinschaftspraxis Dr. Hintsche und Dr. Klausen", 5, isDisplayed())
}

@ExperimentalCoroutinesApi
@InternalCoroutinesApi
@LargeTest
@Test
fun clickOnRecentDoctors_shouldOpenDetails() {
setDispatcher("doctors.json", 200)
activityTestRule.launchActivity(null)

waitUntilViewWithId(R.id.doctors_list_rv, 5, isDisplayed())
waitUntilViewWithText("Gemeinschaftspraxis Dr. Hintsche und Dr. Klausen", 15, isDisplayed())
onView(withId(R.id.doctors_list_rv)).perform(RecyclerViewActions.actionOnItemAtPosition<ItemViewHolder>(3, click()))
waitUntilViewWithId(R.id.name_tv, 15, isDisplayed())
waitUntilViewWithText("Gemeinschaftspraxis Dr. Hintsche und Dr. Klausen", 15, isDisplayed())
pressBack()
onView(withId(R.id.doctor_1_iv)).perform(click())
waitUntilViewWithText("Gemeinschaftspraxis Dr. Hintsche und Dr. Klausen", 15, isDisplayed())
}

private fun setDispatcher(fileName: String, responseCode: Int) {
mockServer.dispatcher = object : Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse {
val path = request.path
if (!path.isNullOrEmpty() && path.contains("doctors-CvQD7gEAAKjcb.json")) {
return MockResponse()
.setResponseCode(responseCode)
.setBody(getStringFrom("doctors-CvQD7gEAAKjcb.json"))
}
return MockResponse()
.setResponseCode(responseCode)
.setBody(getStringFrom(fileName))
}
}
}

@After
fun tearDown() {
mockServer.shutdown()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.codechallenge.doctorscatalog

import androidx.test.espresso.Espresso
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.RootMatchers
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
import androidx.test.uiautomator.UiDevice
import com.codechallenge.doctorscatalog.di.NetworkModule
import com.codechallenge.doctorscatalog.utils.getStringFrom
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.RecordedRequest
import org.hamcrest.Matchers
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
@UninstallModules(NetworkModule::class)
@HiltAndroidTest
class ErrorsTest {

@get:Rule
val hiltRule = HiltAndroidRule(this)

@get:Rule
val activityTestRule = ActivityTestRule(MainActivity::class.java, true, false)

private lateinit var mockServer: MockWebServer

@Before
fun setUp() {
hiltRule.inject()
mockServer = MockWebServer()
mockServer.start(8080)
}

@Test
fun emptyJson_shouldShowErrorMessage() {
setDispatcher("empty.json", 200)
activityTestRule.launchActivity(null)
checkToast("Result can not be parsed")
}

@Test
fun malformedJson_shouldShowErrorMessage() {
setDispatcher("malformed_doctors.json", 200)
activityTestRule.launchActivity(null)
checkToast("Result can not be parsed")
}

@Test
fun networkError_shouldShowErrorMessage() {
setDispatcher("doctors.json", 404)
activityTestRule.launchActivity(null)
checkToast("Network error")
}

private fun setDispatcher(fileName: String, responseCode: Int) {
mockServer.dispatcher = object : Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse {
val path = request.path
if (!path.isNullOrEmpty() && path.contains("doctors-CvQD7gEAAKjcb.json")) {
return MockResponse()
.setResponseCode(responseCode)
.setBody(getStringFrom("doctors-CvQD7gEAAKjcb.json"))
}
return MockResponse()
.setResponseCode(responseCode)
.setBody(getStringFrom(fileName))
}
}
}

private fun checkToast(message: String) {
Espresso.onView(ViewMatchers.withText(message))
.inRoot(
RootMatchers.withDecorView(
Matchers.not(
Matchers.`is`(
activityTestRule.activity.window.decorView
)
)
)
)
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
}

@After
fun tearDown() {
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).setOrientationNatural()
mockServer.shutdown()
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.codechallenge.doctorscatalog

import android.app.Application
import android.content.Context
import androidx.test.runner.AndroidJUnitRunner
import dagger.hilt.android.testing.HiltTestApplication

class MainTestRunner : AndroidJUnitRunner() {
override fun newApplication(
cl: ClassLoader?,
className: String?,
context: Context?
): Application {
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
}
}
Loading

0 comments on commit 72f445d

Please sign in to comment.