- Instrumentation tests added

Took 1 hour 32 minutes
This commit is contained in:
2021-06-13 01:45:10 +01:00
parent 60d0192120
commit f9aac8b755
24 changed files with 323 additions and 76 deletions

Binary file not shown.

View File

@@ -11,7 +11,8 @@ android {
targetSdkVersion 30
versionCode 5
versionName "4.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "com.appttude.h_mal.easycc.application.TestRunner"
}
buildTypes {
release {
@@ -47,6 +48,7 @@ dependencies {
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
androidTestImplementation 'androidx.test:rules:1.4.0-beta02'
//Retrofit and GSON
def retrofit_ver = "2.8.1"

View File

@@ -0,0 +1,31 @@
package com.appttude.h_mal.easycc.application
import android.app.Application
import androidx.test.espresso.idling.CountingIdlingResource
import com.appttude.h_mal.easycc.application.modules.MockRepository
import com.appttude.h_mal.easycc.helper.CurrencyDataHelper
import com.appttude.h_mal.easycc.ui.main.MainViewModelFactory
import org.kodein.di.Kodein
import org.kodein.di.KodeinAware
import org.kodein.di.android.x.androidXModule
import org.kodein.di.generic.bind
import org.kodein.di.generic.instance
import org.kodein.di.generic.provider
import org.kodein.di.generic.singleton
class TestApplication : Application(), KodeinAware {
companion object {
val idlingResources = CountingIdlingResource("Data_loader")
}
// KODEIN DI components declaration
override val kodein by Kodein.lazy {
import(androidXModule(this@TestApplication))
bind() from singleton { MockRepository() }
bind() from singleton { CurrencyDataHelper(instance()) }
bind() from provider { MainViewModelFactory(instance(), instance()) }
}
}

View File

@@ -0,0 +1,22 @@
package com.appttude.h_mal.easycc.application
import android.app.Application
import android.content.Context
import androidx.test.runner.AndroidJUnitRunner
class TestRunner : AndroidJUnitRunner() {
@Throws(
InstantiationException::class,
IllegalAccessException::class,
ClassNotFoundException::class
)
override fun newApplication(
cl: ClassLoader?,
className: String?,
context: Context?
): Application {
return super.newApplication(cl, TestApplication::class.java.name, context)
}
}

View File

@@ -0,0 +1,69 @@
package com.appttude.h_mal.easycc.application.modules
import com.appttude.h_mal.easycc.application.TestApplication.Companion.idlingResources
import com.appttude.h_mal.easycc.data.network.response.CurrencyResponse
import com.appttude.h_mal.easycc.data.network.response.ResponseObject
import com.appttude.h_mal.easycc.data.repository.Repository
import com.appttude.h_mal.easycc.models.CurrencyObject
import kotlinx.coroutines.delay
class MockRepository : Repository {
override suspend fun getDataFromApi(fromCurrency: String, toCurrency: String): ResponseObject {
idlingResources.increment()
delay(500)
return ResponseObject(
results = mapOf(
Pair(
"AUD_GBP", CurrencyObject(
id = "AUD_GBP",
fr = "AUD",
to = "GBP",
value = 0.546181
)
)
)
).also {
idlingResources.decrement()
}
}
override suspend fun getBackupDataFromApi(
fromCurrency: String,
toCurrency: String
): CurrencyResponse {
idlingResources.increment()
delay(500)
return CurrencyResponse(
rates = mapOf(Pair("GBP", 0.54638)),
amount = 1.0,
base = "AUD",
date = "2021-06-11"
).also {
idlingResources.decrement()
}
}
override fun getConversionPair(): Pair<String?, String?> {
return Pair("AUD - Australian Dollar", "GBP - British Pound")
}
override fun setConversionPair(fromCurrency: String, toCurrency: String) {}
override fun getCurrenciesList(): Array<String> =
arrayOf("AUD - Australian Dollar", "GBP - British Pound")
override fun getWidgetConversionPairs(appWidgetId: Int): Pair<String?, String?> {
return Pair(null, null)
}
override fun setWidgetConversionPairs(
fromCurrency: String,
toCurrency: String,
appWidgetId: Int
) {
}
override fun removeWidgetConversionPairs(id: Int) {}
}

View File

@@ -0,0 +1,84 @@
package com.appttude.h_mal.easycc.robots
import android.view.View
import android.view.ViewGroup
import androidx.test.espresso.Espresso
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.ViewMatchers
import com.appttude.h_mal.easycc.R
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.Matchers
import org.hamcrest.TypeSafeMatcher
fun currencyRobot(func: CurrencyRobot.() -> Unit) = CurrencyRobot()
.apply { func() }
class CurrencyRobot {
fun clickOnTopList() {
Espresso.onView(ViewMatchers.withId(R.id.currency_one)).perform(ViewActions.click())
}
fun clickOnBottomList() {
Espresso.onView(ViewMatchers.withId(R.id.currency_two)).perform(ViewActions.click())
}
fun searchInCurrencyList(search: String) {
Espresso.onView(ViewMatchers.withId(R.id.search_text))
.perform(ViewActions.replaceText(search), ViewActions.closeSoftKeyboard())
}
fun enterValueInTopEditText(text: String) {
Espresso.onView(ViewMatchers.withId(R.id.topInsertValue))
.perform(ViewActions.replaceText(text), ViewActions.closeSoftKeyboard())
}
fun selectItemInCurrencyList() {
Espresso.onData(Matchers.anything())
.inAdapterView(
Matchers.allOf(
ViewMatchers.withId(R.id.list_view),
childAtPosition(
ViewMatchers.withClassName(Matchers.`is`("androidx.cardview.widget.CardView")),
0
)
)
)
.atPosition(0)
.perform(ViewActions.click())
}
fun assertTextInTop(text: String) {
Espresso.onView(
ViewMatchers.withId(R.id.topInsertValue)
).check(ViewAssertions.matches(ViewMatchers.withText(text)))
}
fun assertTextInBottom(text: String) {
Espresso.onView(
ViewMatchers.withId(R.id.bottomInsertValues)
).check(ViewAssertions.matches(ViewMatchers.withText(text)))
}
private fun childAtPosition(
parentMatcher: Matcher<View>, position: Int
): Matcher<View> {
return object : TypeSafeMatcher<View>() {
override fun describeTo(description: Description) {
description.appendText("Child at position $position in parent ")
parentMatcher.describeTo(description)
}
public override fun matchesSafely(view: View): Boolean {
val parent = view.parent
return parent is ViewGroup && parentMatcher.matches(parent)
&& view == parent.getChildAt(position)
}
}
}
}

View File

@@ -0,0 +1,35 @@
@file:Suppress("DEPRECATION")
package com.appttude.h_mal.easycc.ui.main
import androidx.test.filters.LargeTest
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.AndroidJUnit4
import com.appttude.h_mal.easycc.robots.currencyRobot
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@LargeTest
@RunWith(AndroidJUnit4::class)
class MainActivityTest {
@Rule
@JvmField
var mActivityTestRule = ActivityTestRule(MainActivity::class.java)
@Test
fun mainActivityTest() {
currencyRobot {
clickOnTopList()
searchInCurrencyList("AUD")
selectItemInCurrencyList()
clickOnBottomList()
searchInCurrencyList("GBP")
selectItemInCurrencyList()
enterValueInTopEditText("1")
assertTextInBottom("0.55")
}
}
}

View File

@@ -30,7 +30,7 @@ class AppClass : Application(), KodeinAware {
bind() from singleton { loggingInterceptor() }
bind() from singleton { QueryInterceptor(instance()) }
bind() from singleton { CurrencyApi(instance(), instance(), instance()) }
bind() from singleton { BackupCurrencyApi(instance(),instance()) }
bind() from singleton { BackupCurrencyApi(instance(), instance()) }
bind() from singleton { PreferenceProvider(instance()) }
bind() from singleton { RepositoryImpl(instance(), instance(), instance()) }
bind() from singleton { CurrencyDataHelper(instance()) }

View File

@@ -4,7 +4,6 @@ import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import androidx.annotation.RequiresApi
import okhttp3.Interceptor
import okhttp3.Response
import java.io.IOException
@@ -21,7 +20,7 @@ class NetworkConnectionInterceptor(
private val applicationContext = context.applicationContext
override fun intercept(chain: Interceptor.Chain): Response {
if (!isInternetAvailable()){
if (!isInternetAvailable()) {
throw IOException("Make sure you have an active data connection")
}
return chain.proceed(chain.request())

View File

@@ -13,11 +13,11 @@ data class CurrencyResponse(
val amount: Double? = null,
@field:SerializedName("rates")
var rates : Map<String, Double>? = null,
var rates: Map<String, Double>? = null,
@field:SerializedName("base")
val base: String? = null
): CurrencyModelInterface {
) : CurrencyModelInterface {
override fun getCurrencyModel(): CurrencyModel {
return CurrencyModel(

View File

@@ -7,10 +7,10 @@ import com.google.gson.annotations.SerializedName
data class ResponseObject(
@field:SerializedName("query")
var query : Any? = null,
var query: Any? = null,
@field:SerializedName("results")
var results : Map<String, CurrencyObject>? = null
): CurrencyModelInterface {
var results: Map<String, CurrencyObject>? = null
) : CurrencyModelInterface {
override fun getCurrencyModel(): CurrencyModel {
val res = results?.iterator()?.next()?.value
@@ -20,5 +20,4 @@ data class ResponseObject(
res?.value ?: 0.0
)
}
}

View File

@@ -8,7 +8,6 @@ import android.view.WindowManager
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.appttude.h_mal.easycc.R
import com.appttude.h_mal.easycc.databinding.ActivityMainBinding

View File

@@ -2,7 +2,7 @@ package com.appttude.h_mal.easycc.ui.main
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.appttude.h_mal.easycc.data.repository.RepositoryImpl
import com.appttude.h_mal.easycc.data.repository.Repository
import com.appttude.h_mal.easycc.helper.CurrencyDataHelper
/**
@@ -11,7 +11,7 @@ import com.appttude.h_mal.easycc.helper.CurrencyDataHelper
*/
@Suppress("UNCHECKED_CAST")
class MainViewModelFactory(
private val repository: RepositoryImpl,
private val repository: Repository,
private val helper: CurrencyDataHelper
) : ViewModelProvider.NewInstanceFactory() {

View File

@@ -8,7 +8,6 @@ import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.appttude.h_mal.easycc.R
import com.appttude.h_mal.easycc.databinding.CurrencyAppWidgetConfigureBinding

View File

@@ -3,11 +3,11 @@ package com.appttude.h_mal.easycc.utils
import java.lang.Double.valueOf
import java.text.DecimalFormat
fun transformIntToArray(int: Int): IntArray{
fun transformIntToArray(int: Int): IntArray {
return intArrayOf(int)
}
fun String.trimToThree(): String{
fun String.trimToThree(): String {
val size = length
return when {
size > 3 -> substring(0, 3)
@@ -22,13 +22,13 @@ fun Double.toTwoDp() = run {
try {
val df = DecimalFormat("0.00")
valueOf(df.format(this))
}catch (e: NumberFormatException){
} catch (e: NumberFormatException) {
e.printStackTrace()
this
}
}
fun Double.toTwoDpString(): String{
fun Double.toTwoDpString(): String {
return this.toTwoDp().toBigDecimal().toPlainString()
}

View File

@@ -5,5 +5,5 @@
android:startColor="#b3001b"
android:endColor="#262626"
android:type="linear"
android:angle="45"/>
android:angle="45" />
</shape>

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" android:padding="10dp">
android:shape="rectangle"
android:padding="10dp">
<solid android:color="@color/colour_two" />
<corners
android:bottomRightRadius="22dp"

View File

@@ -4,8 +4,7 @@
android:height="192dp"
android:viewportWidth="1080"
android:viewportHeight="1920">
<path
android:pathData="M0,0L1080,0L1080,1920L0,1920L0,0Z">
<path android:pathData="M0,0L1080,0L1080,1920L0,1920L0,0Z">
<aapt:attr name="android:fillColor">
<gradient
android:startY="0"
@@ -13,8 +12,12 @@
android:endY="1920"
android:endX="0"
android:type="linear">
<item android:offset="0" android:color="#FF315659"/>
<item android:offset="1" android:color="#FF2978A0"/>
<item
android:offset="0"
android:color="#FF315659" />
<item
android:offset="1"
android:color="#FF2978A0" />
</gradient>
</aapt:attr>
</path>

View File

@@ -1,5 +1,10 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
<vector android:height="24dp"
android:tint="#FFFFFF"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="#FF000000"
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z" />
</vector>

View File

@@ -8,10 +8,10 @@
android:background="#4D000000">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/exchangeName"
android:layout_width="match_parent"
@@ -24,6 +24,7 @@
android:textColor="#ffffff"
android:text="Rate not set"
tools:text="AUDGBP" />
<ImageView
android:id="@+id/refresh_icon"
android:layout_width="18dp"
@@ -32,7 +33,7 @@
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_alignBottom="@id/exchangeName"
android:adjustViewBounds="true"/>
android:adjustViewBounds="true" />
</RelativeLayout>
<TextView
@@ -52,13 +53,13 @@
android:textStyle="bold"
tools:text="0.52646215" />
<!-- <TextView-->
<!-- android:id="@+id/lastUpdated"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:gravity="center"-->
<!-- android:textSize="8sp"-->
<!-- android:textColor="#ffffff"-->
<!-- android:text=" "-->
<!-- tools:text="Last updated: 03:18 Wed" />-->
<!-- <TextView-->
<!-- android:id="@+id/lastUpdated"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:gravity="center"-->
<!-- android:textSize="8sp"-->
<!-- android:textColor="#ffffff"-->
<!-- android:text=" "-->
<!-- tools:text="Last updated: 03:18 Wed" />-->
</LinearLayout>

View File

@@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewmodel"
type="com.appttude.h_mal.easycc.ui.widget.WidgetViewModel" />
@@ -78,6 +79,5 @@
</RelativeLayout>
</RelativeLayout>
</layout>

View File

@@ -1,13 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:backgroundTint="@android:color/transparent"
android:orientation="vertical">
<androidx.cardview.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_weight="1"
android:layout_height="0dp"
android:layout_width="match_parent"
@@ -18,10 +16,10 @@
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
android:layout_height="match_parent"></ListView>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@@ -8,4 +8,4 @@
android:previewImage="@drawable/example_appwidget_preview"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="3600000"
android:widgetCategory="home_screen|keyguard"/>
android:widgetCategory="home_screen|keyguard" />