- Modularised the app to share more setup

- Fixed failing UI tests
This commit is contained in:
2024-07-03 17:24:35 +01:00
parent 9141db3afe
commit 024488406c
15 changed files with 87 additions and 103 deletions

View File

@@ -92,8 +92,18 @@ open class BaseTest<A : Activity>(
testApp.stubLocation(location, lat, long)
}
fun clearLocation(location: String) {
testApp.removeLocation(location)
}
fun clearLocation(lat: Double, long: Double) {
testApp.removeLocation(lat, long)
}
fun clearPrefs() = prefs.clearPrefs()
fun clearDatabase() = testApp.clearDatabase()
fun getActivity() = testActivity
@After

View File

@@ -14,9 +14,13 @@ import com.appttude.h_mal.atlas_weather.data.network.interceptors.QueryParamsInt
import com.appttude.h_mal.atlas_weather.data.network.networkUtils.loggingInterceptor
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
import com.appttude.h_mal.atlas_weather.data.room.Converter
import org.kodein.di.LazyKodein
import java.io.BufferedReader
class TestAppClass : AtlasApp() {
class TestAppClass : AppClass() {
override val kodein: LazyKodein = super.kodein
private val idlingResources = CountingIdlingResource("Data_loader")
private val mockingNetworkInterceptor = MockingNetworkInterceptor(idlingResources)
@@ -64,4 +68,15 @@ class TestAppClass : AtlasApp() {
locationProvider.addLocationToList(location, lat, long)
}
fun removeLocation(location: String) {
locationProvider.removeLocationFromList(location)
}
fun removeLocation(lat: Double, long: Double) {
locationProvider.removeLocationFromList(lat, long)
}
fun clearDatabase() {
database.getWeatherDao().deleteAll()
}
}

View File

@@ -22,6 +22,16 @@ class MockLocationProvider : LocationProvider {
fun addLocationToList(name: String, lat: Double, long: Double) {
val latLong = Pair(lat, long)
feedMap.put(name, latLong)
feedMap[name] = latLong
}
fun removeLocationFromList(name: String) {
feedMap.remove(name)
}
fun removeLocationFromList(lat: Double, long: Double) {
feedMap.filterValues { it.first == lat && it.second == long }.keys.firstOrNull()?.let {
feedMap.remove(it)
}
}
}

View File

@@ -22,6 +22,13 @@ class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) {
clearPrefs()
}
override fun testFinished() {
super.testFinished()
clearLocation("London")
clearDatabase()
clearPrefs()
}
@Test
fun homeAndFurtherInfoPageCapture() {
weatherScreen {

View File

@@ -1,67 +0,0 @@
package com.appttude.h_mal.atlas_weather.application
import androidx.room.Room
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.idling.CountingIdlingResource
import androidx.test.platform.app.InstrumentationRegistry
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
import com.appttude.h_mal.atlas_weather.data.location.MockLocationProvider
import com.appttude.h_mal.atlas_weather.data.network.NetworkModule
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
import com.appttude.h_mal.atlas_weather.data.network.interceptors.MockingNetworkInterceptor
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkConnectionInterceptor
import com.appttude.h_mal.atlas_weather.data.network.interceptors.QueryParamsInterceptor
import com.appttude.h_mal.atlas_weather.data.network.networkUtils.loggingInterceptor
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
import com.appttude.h_mal.atlas_weather.data.room.Converter
import java.io.BufferedReader
class TestAppClass : MonoApp() {
private val idlingResources = CountingIdlingResource("Data_loader")
private val mockingNetworkInterceptor = MockingNetworkInterceptor(idlingResources)
lateinit var database: AppDatabase
private val locationProvider: MockLocationProvider = MockLocationProvider()
override fun onCreate() {
super.onCreate()
IdlingRegistry.getInstance().register(idlingResources)
}
override fun createNetworkModule(): WeatherApi {
return NetworkModule().invoke<WeatherApi>(
mockingNetworkInterceptor,
NetworkConnectionInterceptor(this),
QueryParamsInterceptor(),
loggingInterceptor
) as WeatherApi
}
override fun createLocationModule(): LocationProvider {
return locationProvider
}
override fun createRoomDatabase(): AppDatabase {
database = Room.inMemoryDatabaseBuilder(applicationContext, AppDatabase::class.java)
.allowMainThreadQueries()
.addTypeConverter(Converter(this))
.build()
return database
}
fun stubUrl(url: String, rawPath: String, code: Int = 200) {
val iStream =
InstrumentationRegistry.getInstrumentation().context.assets.open("$rawPath.json")
val data = iStream.bufferedReader().use(BufferedReader::readText)
mockingNetworkInterceptor.addUrlStub(url = url, data = data, code = code)
}
fun removeUrlStub(url: String) {
mockingNetworkInterceptor.removeUrlStub(url = url)
}
fun stubLocation(location: String, lat: Double = 0.00, long: Double = 0.00) {
locationProvider.addLocationToList(location, lat, long)
}
}

View File

@@ -9,6 +9,7 @@ import com.appttude.h_mal.atlas_weather.utils.Stubs
import com.appttude.h_mal.monoWeather.robot.furtherInfoScreen
import com.appttude.h_mal.monoWeather.robot.settingsScreen
import com.appttude.h_mal.monoWeather.robot.weatherScreen
import org.junit.Ignore
import org.junit.Test
import tools.fastlane.screengrab.Screengrab
@@ -22,6 +23,12 @@ class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) {
clearPrefs()
}
override fun testFinished() {
super.testFinished()
clearLocation("London")
clearDatabase()
}
@Test
fun homeAndFurtherInfoPageCapture() {

View File

@@ -8,6 +8,7 @@ import com.appttude.h_mal.atlas_weather.utils.Stubs
import com.appttude.h_mal.monoWeather.robot.furtherInfoScreen
import com.appttude.h_mal.monoWeather.robot.settingsScreen
import com.appttude.h_mal.monoWeather.robot.weatherScreen
import okio.IOException
import org.junit.Test
import tools.fastlane.screengrab.Screengrab
@@ -40,7 +41,6 @@ class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
verifyMaxTemperature(12)
verifyAverageTemperature(9)
}
}
@Test

View File

@@ -5,7 +5,7 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:name="com.appttude.h_mal.atlas_weather.application.AtlasApp"
android:name="com.appttude.h_mal.atlas_weather.application.AppClass"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"

View File

@@ -1,18 +1,18 @@
package com.appttude.h_mal.atlas_weather.application
import android.app.Application
import com.appttude.h_mal.atlas_weather.service.notification.NotificationHelper
import com.appttude.h_mal.atlas_weather.service.notification.NotificationService
import org.kodein.di.Kodein
import org.kodein.di.generic.bind
import org.kodein.di.generic.instance
import org.kodein.di.generic.provider
import org.kodein.di.generic.singleton
open class AtlasApp : AppClass() {
private lateinit var notificationService: NotificationService
override val flavourModule = super.flavourModule.copy {
fun getFlavourModule(application: Application) = FlavourModule(application).build()
class FlavourModule(val application: Application) {
fun build() = Kodein.Module("Flavour") {
bind() from singleton {
NotificationHelper(
instance(),
@@ -21,12 +21,12 @@ open class AtlasApp : AppClass() {
}
bind() from singleton {
NotificationService(this@AtlasApp).apply { notificationService = this }
NotificationService(application)
}
bind() from provider {
ApplicationViewModelFactory(
this@AtlasApp,
application,
instance(),
instance(),
instance(),
@@ -34,13 +34,4 @@ open class AtlasApp : AppClass() {
)
}
}
// override fun onCreate() {
// super.onCreate()
// notificationService.schedulePushNotifications()
// }
fun scheduleNotifications() = notificationService.schedulePushNotifications()
fun unscheduleNotifications() = notificationService.unschedulePushNotifications()
}

View File

@@ -15,16 +15,16 @@ import androidx.navigation.ui.onNavDestinationSelected
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.appttude.h_mal.atlas_weather.R
import com.appttude.h_mal.atlas_weather.application.AtlasApp
import com.appttude.h_mal.atlas_weather.base.BaseFragment
import com.appttude.h_mal.atlas_weather.model.forecast.Forecast
import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay
import com.appttude.h_mal.atlas_weather.service.notification.NotificationService
import com.appttude.h_mal.atlas_weather.ui.dialog.PermissionsDeclarationDialog
import com.appttude.h_mal.atlas_weather.ui.home.adapter.WeatherRecyclerAdapter
import com.appttude.h_mal.atlas_weather.utils.displayToast
import com.appttude.h_mal.atlas_weather.utils.navigateTo
import com.appttude.h_mal.atlas_weather.viewmodel.MainViewModel
import org.kodein.di.generic.instance
import permissions.dispatcher.NeedsPermission
import permissions.dispatcher.OnNeverAskAgain
import permissions.dispatcher.OnPermissionDenied
@@ -40,6 +40,8 @@ import permissions.dispatcher.RuntimePermissions
@RuntimePermissions
class HomeFragment : BaseFragment<MainViewModel>(R.layout.fragment_home) {
private val notificationService by instance<NotificationService>()
private lateinit var recyclerAdapter: WeatherRecyclerAdapter
private lateinit var swipeRefresh: SwipeRefreshLayout
@@ -100,7 +102,11 @@ class HomeFragment : BaseFragment<MainViewModel>(R.layout.fragment_home) {
}
@Deprecated("Deprecated in Java")
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
// NOTE: delegate the permission handling to generated method
onRequestPermissionsResult(requestCode, grantResults)
@@ -110,7 +116,7 @@ class HomeFragment : BaseFragment<MainViewModel>(R.layout.fragment_home) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
sendNotification()
} else {
(requireActivity().application as AtlasApp).scheduleNotifications()
notificationService.schedulePushNotifications()
}
}
@@ -142,7 +148,7 @@ class HomeFragment : BaseFragment<MainViewModel>(R.layout.fragment_home) {
@SuppressLint("MissingPermission")
@NeedsPermission(POST_NOTIFICATIONS)
fun sendNotification() {
(requireActivity().application as AtlasApp).scheduleNotifications()
notificationService.schedulePushNotifications()
}
@OnShowRationale(POST_NOTIFICATIONS)

View File

@@ -12,6 +12,7 @@ import com.appttude.h_mal.atlas_weather.helper.ServicesHelper
import com.google.gson.Gson
import org.kodein.di.Kodein
import org.kodein.di.KodeinAware
import org.kodein.di.KodeinContainer
import org.kodein.di.android.x.androidXModule
import org.kodein.di.generic.bind
import org.kodein.di.generic.instance
@@ -22,7 +23,7 @@ abstract class BaseAppClass : Application(), KodeinAware {
// Kodein aware to initialise the classes used for DI
override val kodein = Kodein.lazy {
import(parentModule)
import(flavourModule)
import(getFlavourModule(application = this@BaseAppClass))
}
val parentModule = Kodein.Module("Parent Module", allowSilentOverride = true) {
@@ -40,10 +41,6 @@ abstract class BaseAppClass : Application(), KodeinAware {
bind() from singleton { WeatherSource(instance(), instance()) }
}
open val flavourModule = Kodein.Module("Flavour") {
import(parentModule)
}
abstract fun createNetworkModule(): WeatherApi
abstract fun createLocationModule(): LocationProvider
abstract fun createRoomDatabase(): AppDatabase

View File

@@ -1,7 +1,9 @@
package com.appttude.h_mal.atlas_weather.data.room
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
@@ -32,4 +34,7 @@ interface WeatherDao {
@Query("DELETE FROM EntityItem WHERE id = :userId")
fun deleteEntry(userId: String): Int
@VisibleForTesting
@Query("DELETE FROM EntityItem")
fun deleteAll(): Int
}

View File

@@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">
<application
android:name="com.appttude.h_mal.atlas_weather.application.MonoApp"
android:name="com.appttude.h_mal.atlas_weather.application.AppClass"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"

View File

@@ -1,15 +1,18 @@
package com.appttude.h_mal.atlas_weather.application
import android.app.Application
import org.kodein.di.Kodein
import org.kodein.di.generic.bind
import org.kodein.di.generic.instance
import org.kodein.di.generic.provider
open class MonoApp : AppClass() {
override val flavourModule = super.flavourModule.copy {
fun getFlavourModule(application: Application) = FlavourModule(application).build()
class FlavourModule(val application: Application) {
fun build() = Kodein.Module("Flavour") {
bind() from provider {
ApplicationViewModelFactory(
this@MonoApp,
application,
instance(),
instance(),
instance(),