- Tests passed

- Weather API successfully replaces
This commit is contained in:
2024-10-02 00:17:29 +01:00
parent 6e28db5feb
commit 406402111b
35 changed files with 43142 additions and 11729 deletions

View File

@@ -18,8 +18,8 @@ android {
applicationId "com.appttude.h_mal.atlas_weather" applicationId "com.appttude.h_mal.atlas_weather"
minSdkVersion MIN_SDK_VERSION minSdkVersion MIN_SDK_VERSION
targetSdkVersion TARGET_SDK_VERSION targetSdkVersion TARGET_SDK_VERSION
versionCode 5 versionCode 6
versionName "3.0" versionName "3.1"
testInstrumentationRunner "com.appttude.h_mal.atlas_weather.application.TestRunner" testInstrumentationRunner "com.appttude.h_mal.atlas_weather.application.TestRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@@ -32,6 +32,10 @@ android {
buildConfigField "String", "ParamOne", System.getenv('WEATHER_API') buildConfigField "String", "ParamOne", System.getenv('WEATHER_API')
buildConfigField "String", "ParamTwo", System.getenv('SEARCH_API') buildConfigField "String", "ParamTwo", System.getenv('SEARCH_API')
} }
packagingOptions {
resources.excludes.add("META-INF/*")
}
} }
android { android {
sourceSets { sourceSets {

View File

@@ -1,4 +0,0 @@
{
"cod": 401,
"message": "Invalid API key. Please see http://openweathermap.org/faq#error401 for more info."
}

View File

@@ -0,0 +1 @@
No account found with API key 'wrong api key'

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -67,7 +67,7 @@ open class BaseTest<A : Activity>(
} }
testApp = testApp =
InstrumentationRegistry.getInstrumentation().targetContext.applicationContext InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as TestAppClass
runBlocking { runBlocking {
beforeLaunch() beforeLaunch()
} }
@@ -80,8 +80,8 @@ open class BaseTest<A : Activity>(
afterLaunch() afterLaunch()
} }
fun stubEndpoint(url: String, stub: Stubs, code: Int = 200) { fun stubEndpoint(url: String, stub: Stubs, code: Int = 200, extension: String = ".json") {
testApp.stubUrl(url, stub.id, code) testApp.stubUrl(url, stub.id, code, extension)
} }
fun unstubEndpoint(url: String) { fun unstubEndpoint(url: String) {
@@ -125,7 +125,6 @@ open class BaseTest<A : Activity>(
}) })
} }
@Suppress("DEPRECATION")
fun checkToastMessage(message: String) { fun checkToastMessage(message: String) {
Espresso.onView(ViewMatchers.withText(message)).inRoot(withDecorView(Matchers.not(decorView))) Espresso.onView(ViewMatchers.withText(message)).inRoot(withDecorView(Matchers.not(decorView)))
.check(ViewAssertions.matches(ViewMatchers.isDisplayed())) .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))

View File

@@ -17,7 +17,12 @@ import androidx.test.espresso.action.ViewActions.swipeDown
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.PickerActions import androidx.test.espresso.contrib.PickerActions
import androidx.test.espresso.contrib.RecyclerViewActions import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers.* import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.withClassName
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView
import com.appttude.h_mal.atlas_weather.helpers.checkErrorMessage import com.appttude.h_mal.atlas_weather.helpers.checkErrorMessage
import com.appttude.h_mal.atlas_weather.helpers.checkImage import com.appttude.h_mal.atlas_weather.helpers.checkImage

View File

@@ -16,9 +16,9 @@ class MockingNetworkInterceptor(
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
idlingResource.increment() idlingResource.increment()
val original = chain.request() val original = chain.request()
val originalHttpUrl = original.url.toString().split("?")[0] val originalHttpUrl = original.url.toString()
feedMap[originalHttpUrl]?.let { responsePair -> feedMap[feedMap.keys.first { originalHttpUrl.contains(it) }]?.let { responsePair ->
val code = responsePair.second val code = responsePair.second
val jsonBody = responsePair.first val jsonBody = responsePair.first

View File

@@ -9,5 +9,4 @@ enum class Stubs(
WrongLocation("wrong_location_response"), WrongLocation("wrong_location_response"),
InvalidKey("invalid_api_key_response"), InvalidKey("invalid_api_key_response"),
Sydney("valid_response_metric_sydney"), Sydney("valid_response_metric_sydney"),
New("new_response")
} }

View File

@@ -7,7 +7,7 @@ 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.LocationProvider
import com.appttude.h_mal.atlas_weather.data.location.MockLocationProvider 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.NetworkModule
import com.appttude.h_mal.atlas_weather.data.network.NewWeatherApi 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.MockingNetworkInterceptor
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkConnectionInterceptor 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.interceptors.QueryParamsInterceptor
@@ -28,8 +28,8 @@ class TestAppClass : AppClass() {
IdlingRegistry.getInstance().register(idlingResources) IdlingRegistry.getInstance().register(idlingResources)
} }
override fun createNetworkModule(): NewWeatherApi { override fun createNetworkModule(): WeatherApi {
return NetworkModule().invoke<NewWeatherApi>( return NetworkModule().invoke<WeatherApi>(
mockingNetworkInterceptor, mockingNetworkInterceptor,
NetworkConnectionInterceptor(this), NetworkConnectionInterceptor(this),
QueryParamsInterceptor(), QueryParamsInterceptor(),
@@ -49,9 +49,9 @@ class TestAppClass : AppClass() {
return database return database
} }
fun stubUrl(url: String, rawPath: String, code: Int = 200) { fun stubUrl(url: String, rawPath: String, code: Int = 200, extension: String = ".json") {
val iStream = val iStream =
InstrumentationRegistry.getInstrumentation().context.assets.open("$rawPath.json") InstrumentationRegistry.getInstrumentation().context.assets.open("$rawPath$extension")
val data = iStream.bufferedReader().use(BufferedReader::readText) val data = iStream.bufferedReader().use(BufferedReader::readText)
mockingNetworkInterceptor.addUrlStub(url = url, data = data, code = code) mockingNetworkInterceptor.addUrlStub(url = url, data = data, code = code)
} }

View File

@@ -13,6 +13,7 @@ import com.appttude.h_mal.atlas_weather.BaseTestRobot
import com.appttude.h_mal.atlas_weather.R import com.appttude.h_mal.atlas_weather.R
import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView
import com.appttude.h_mal.atlas_weather.model.types.UnitType import com.appttude.h_mal.atlas_weather.model.types.UnitType
import com.appttude.h_mal.atlas_weather.model.types.UnitType.Companion.getLabel
fun settingsScreen(func: SettingsScreen.() -> Unit) = SettingsScreen().apply { func() } fun settingsScreen(func: SettingsScreen.() -> Unit) = SettingsScreen().apply { func() }

View File

@@ -9,6 +9,7 @@ import com.appttude.h_mal.atlas_weather.utils.Stubs
import com.appttude.h_mal.atlas_weather.robot.furtherInfoScreen import com.appttude.h_mal.atlas_weather.robot.furtherInfoScreen
import com.appttude.h_mal.atlas_weather.robot.settingsScreen import com.appttude.h_mal.atlas_weather.robot.settingsScreen
import com.appttude.h_mal.atlas_weather.robot.weatherScreen import com.appttude.h_mal.atlas_weather.robot.weatherScreen
import com.appttude.h_mal.atlas_weather.utils.baseUrl
import org.junit.Test import org.junit.Test
import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.Screengrab
@@ -17,7 +18,7 @@ import tools.fastlane.screengrab.Screengrab
class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) { class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) {
override fun beforeLaunch() { override fun beforeLaunch() {
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric) stubEndpoint(baseUrl, Stubs.Metric)
stubLocation("London", 51.51, -0.13) stubLocation("London", 51.51, -0.13)
clearPrefs() clearPrefs()
} }
@@ -50,7 +51,7 @@ class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) {
openMenuItem() openMenuItem()
} }
settingsScreen { settingsScreen {
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Imperial) stubEndpoint(baseUrl, Stubs.Imperial)
Screengrab.screenshot("SettingsScreen") Screengrab.screenshot("SettingsScreen")
} }
} }

View File

@@ -1,36 +1,27 @@
package com.appttude.h_mal.atlas_weather.tests package com.appttude.h_mal.atlas_weather.tests
import com.appttude.h_mal.atlas_weather.BaseTest import com.appttude.h_mal.atlas_weather.BaseTest
import com.appttude.h_mal.atlas_weather.robot.homeScreen
import com.appttude.h_mal.atlas_weather.ui.MainActivity import com.appttude.h_mal.atlas_weather.ui.MainActivity
import com.appttude.h_mal.atlas_weather.utils.Stubs import com.appttude.h_mal.atlas_weather.utils.Stubs
import com.appttude.h_mal.atlas_weather.robot.homeScreen import com.appttude.h_mal.atlas_weather.utils.baseUrl
import org.junit.Test import org.junit.Test
class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) { class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) {
override fun beforeLaunch() { override fun beforeLaunch() {
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.InvalidKey, 400) stubEndpoint(baseUrl, Stubs.InvalidKey, 400, ".txt")
}
@Test
fun loadApp_invalidKeyWeatherResponse_returnsEmptyViewPage() {
homeScreen {
waitFor(2000)
// verify empty
verifyUnableToRetrieve()
}
} }
@Test @Test
fun invalidKeyWeatherResponse_swipeToRefresh_returnsValidPage() { fun invalidKeyWeatherResponse_swipeToRefresh_returnsValidPage() {
homeScreen { homeScreen {
waitFor(2000)
// verify empty // verify empty
verifyUnableToRetrieve() verifyUnableToRetrieve()
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric) stubEndpoint(baseUrl, Stubs.Metric)
refresh() refresh()
verifyCurrentTemperature(2) verifyCurrentTemperature(13)
verifyCurrentLocation("Mock Location") verifyCurrentLocation("Mock Location")
} }
} }

View File

@@ -5,19 +5,20 @@ import com.appttude.h_mal.atlas_weather.BaseTest
import com.appttude.h_mal.atlas_weather.robot.homeScreen import com.appttude.h_mal.atlas_weather.robot.homeScreen
import com.appttude.h_mal.atlas_weather.ui.MainActivity import com.appttude.h_mal.atlas_weather.ui.MainActivity
import com.appttude.h_mal.atlas_weather.utils.Stubs import com.appttude.h_mal.atlas_weather.utils.Stubs
import com.appttude.h_mal.atlas_weather.utils.baseUrl
import org.junit.Test import org.junit.Test
class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) { class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
override fun beforeLaunch() { override fun beforeLaunch() {
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric) stubEndpoint(baseUrl, Stubs.Metric)
} }
@Test @Test
fun loadApp_validWeatherResponse_returnsValidPage() { fun loadApp_validWeatherResponse_returnsValidPage() {
homeScreen { homeScreen {
isDisplayed() isDisplayed()
verifyCurrentTemperature(2) verifyCurrentTemperature(13)
verifyCurrentLocation("Mock Location") verifyCurrentLocation("Mock Location")
} }
} }

View File

@@ -7,7 +7,7 @@ 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.LocationProvider
import com.appttude.h_mal.atlas_weather.data.location.MockLocationProvider 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.NetworkModule
import com.appttude.h_mal.atlas_weather.data.network.NewWeatherApi 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.MockingNetworkInterceptor
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkConnectionInterceptor 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.interceptors.QueryParamsInterceptor
@@ -18,7 +18,6 @@ import org.kodein.di.LazyKodein
import java.io.BufferedReader import java.io.BufferedReader
class TestAppClass : AppClass() { class TestAppClass : AppClass() {
override val kodein: LazyKodein = super.kodein override val kodein: LazyKodein = super.kodein
private val idlingResources = CountingIdlingResource("Data_loader") private val idlingResources = CountingIdlingResource("Data_loader")
@@ -32,13 +31,13 @@ class TestAppClass : AppClass() {
IdlingRegistry.getInstance().register(idlingResources) IdlingRegistry.getInstance().register(idlingResources)
} }
override fun createNetworkModule(): NewWeatherApi { override fun createNetworkModule(): WeatherApi {
return NetworkModule().invoke<NewWeatherApi>( return NetworkModule().invoke<WeatherApi>(
mockingNetworkInterceptor, mockingNetworkInterceptor,
NetworkConnectionInterceptor(this), NetworkConnectionInterceptor(this),
QueryParamsInterceptor(), QueryParamsInterceptor(),
loggingInterceptor loggingInterceptor
) as NewWeatherApi ) as WeatherApi
} }
override fun createLocationModule(): LocationProvider { override fun createLocationModule(): LocationProvider {
@@ -53,9 +52,9 @@ class TestAppClass : AppClass() {
return database return database
} }
fun stubUrl(url: String, rawPath: String, code: Int = 200) { fun stubUrl(url: String, rawPath: String, code: Int = 200, extension: String = ".json") {
val iStream = val iStream =
InstrumentationRegistry.getInstrumentation().context.assets.open("$rawPath.json") InstrumentationRegistry.getInstrumentation().context.assets.open("$rawPath$extension")
val data = iStream.bufferedReader().use(BufferedReader::readText) val data = iStream.bufferedReader().use(BufferedReader::readText)
mockingNetworkInterceptor.addUrlStub(url = url, data = data, code = code) mockingNetworkInterceptor.addUrlStub(url = url, data = data, code = code)
} }

View File

@@ -10,7 +10,6 @@ import com.appttude.h_mal.atlas_weather.utils.baseUrl
import com.appttude.h_mal.monoWeather.robot.furtherInfoScreen import com.appttude.h_mal.monoWeather.robot.furtherInfoScreen
import com.appttude.h_mal.monoWeather.robot.settingsScreen import com.appttude.h_mal.monoWeather.robot.settingsScreen
import com.appttude.h_mal.monoWeather.robot.weatherScreen import com.appttude.h_mal.monoWeather.robot.weatherScreen
import org.junit.Ignore
import org.junit.Test import org.junit.Test
import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.Screengrab
@@ -19,7 +18,7 @@ import tools.fastlane.screengrab.Screengrab
class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) { class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) {
override fun beforeLaunch() { override fun beforeLaunch() {
stubEndpoint(baseUrl, Stubs.New) stubEndpoint(baseUrl, Stubs.Metric)
stubLocation("London", 51.5064, -0.12721) stubLocation("London", 51.5064, -0.12721)
clearPrefs() clearPrefs()
} }
@@ -53,7 +52,7 @@ class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) {
openMenuItem() openMenuItem()
} }
settingsScreen { settingsScreen {
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Imperial) stubEndpoint(baseUrl, Stubs.Imperial)
Screengrab.screenshot("SettingsScreen") Screengrab.screenshot("SettingsScreen")
} }
} }

View File

@@ -3,7 +3,6 @@ package com.appttude.h_mal.monoWeather.robot
import com.appttude.h_mal.atlas_weather.BaseTestRobot import com.appttude.h_mal.atlas_weather.BaseTestRobot
import com.appttude.h_mal.atlas_weather.R import com.appttude.h_mal.atlas_weather.R
import com.appttude.h_mal.monoWeather.ui.home.adapter.forecastDaily.ViewHolderForecastDaily import com.appttude.h_mal.monoWeather.ui.home.adapter.forecastDaily.ViewHolderForecastDaily
import com.appttude.h_mal.monoWeather.ui.home.adapter.further.ViewHolderFurtherDetails
fun weatherScreen(func: WeatherScreen.() -> Unit) = WeatherScreen().apply { func() } fun weatherScreen(func: WeatherScreen.() -> Unit) = WeatherScreen().apply { func() }
class WeatherScreen : BaseTestRobot() { class WeatherScreen : BaseTestRobot() {

View File

@@ -4,36 +4,30 @@ package com.appttude.h_mal.monoWeather.tests
import com.appttude.h_mal.atlas_weather.BaseTest import com.appttude.h_mal.atlas_weather.BaseTest
import com.appttude.h_mal.atlas_weather.ui.MainActivity import com.appttude.h_mal.atlas_weather.ui.MainActivity
import com.appttude.h_mal.atlas_weather.utils.Stubs import com.appttude.h_mal.atlas_weather.utils.Stubs
import com.appttude.h_mal.atlas_weather.utils.baseUrl
import com.appttude.h_mal.monoWeather.robot.weatherScreen import com.appttude.h_mal.monoWeather.robot.weatherScreen
import org.junit.Ignore import org.junit.FixMethodOrder
import org.junit.Test import org.junit.Test
import org.junit.runners.MethodSorters
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) { class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) {
override fun beforeLaunch() { override fun beforeLaunch() {
// Todo: change this stubEndpoint(baseUrl, Stubs.InvalidKey, 400, ".txt")
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.InvalidKey, 400)
} }
@Test
fun loadApp_invalidKeyWeatherResponse_returnsEmptyViewPage() {
weatherScreen {
// verify empty
verifyUnableToRetrieve()
}
}
@Ignore("Test is flakey - must investigate")
@Test @Test
fun invalidKeyWeatherResponse_swipeToRefresh_returnsValidPage() { fun invalidKeyWeatherResponse_swipeToRefresh_returnsValidPage() {
weatherScreen { weatherScreen {
// verify empty // verify empty
verifyUnableToRetrieve() verifyUnableToRetrieve()
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric) stubEndpoint(baseUrl, Stubs.Metric)
refresh() refresh()
verifyCurrentTemperature(2) verifyCurrentTemperature(13)
verifyCurrentLocation("Mock Location") verifyCurrentLocation("Mock Location")
} }
} }
} }

View File

@@ -2,19 +2,17 @@ package com.appttude.h_mal.monoWeather.tests
import com.appttude.h_mal.atlas_weather.BaseTest import com.appttude.h_mal.atlas_weather.BaseTest
import com.appttude.h_mal.atlas_weather.model.types.UnitType
import com.appttude.h_mal.atlas_weather.ui.MainActivity import com.appttude.h_mal.atlas_weather.ui.MainActivity
import com.appttude.h_mal.atlas_weather.utils.Stubs import com.appttude.h_mal.atlas_weather.utils.Stubs
import com.appttude.h_mal.atlas_weather.utils.baseUrl import com.appttude.h_mal.atlas_weather.utils.baseUrl
import com.appttude.h_mal.monoWeather.robot.furtherInfoScreen 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 com.appttude.h_mal.monoWeather.robot.weatherScreen
import org.junit.Test import org.junit.Test
class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) { class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
override fun beforeLaunch() { override fun beforeLaunch() {
stubEndpoint(baseUrl, Stubs.New) stubEndpoint(baseUrl, Stubs.Metric)
clearPrefs() clearPrefs()
} }
@@ -22,7 +20,7 @@ class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
fun loadApp_validWeatherResponse_returnsValidPage() { fun loadApp_validWeatherResponse_returnsValidPage() {
weatherScreen { weatherScreen {
isDisplayed() isDisplayed()
verifyCurrentTemperature(2) verifyCurrentTemperature(13)
verifyCurrentLocation("Mock Location") verifyCurrentLocation("Mock Location")
} }
} }
@@ -31,35 +29,15 @@ class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
fun loadApp_validWeatherResponse_viewFurtherDetailsPage() { fun loadApp_validWeatherResponse_viewFurtherDetailsPage() {
weatherScreen { weatherScreen {
isDisplayed() isDisplayed()
verifyCurrentTemperature(2) verifyCurrentTemperature(13)
verifyCurrentLocation("Mock Location") verifyCurrentLocation("Mock Location")
tapDayInformationByPosition(4) tapDayInformationByPosition(4)
} }
furtherInfoScreen { furtherInfoScreen {
isDisplayed() isDisplayed()
verifyMaxTemperature(12) verifyMaxTemperature(15)
verifyAverageTemperature(9) verifyAverageTemperature(11)
} }
} }
@Test
fun loadApp_changeToImperial_returnsValidPage() {
weatherScreen {
isDisplayed()
verifyCurrentTemperature(2)
verifyCurrentLocation("Mock Location")
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Imperial)
openMenuItem()
}
settingsScreen {
selectWeatherUnits(UnitType.IMPERIAL)
goBack()
}
weatherScreen {
isDisplayed()
refresh()
verifyCurrentTemperature(58)
verifyCurrentLocation("Mock Location")
}
}
} }

View File

@@ -0,0 +1,40 @@
package com.appttude.h_mal.monoWeather.tests
import com.appttude.h_mal.atlas_weather.BaseTest
import com.appttude.h_mal.atlas_weather.model.types.UnitType
import com.appttude.h_mal.atlas_weather.ui.MainActivity
import com.appttude.h_mal.atlas_weather.utils.Stubs
import com.appttude.h_mal.atlas_weather.utils.baseUrl
import com.appttude.h_mal.monoWeather.robot.settingsScreen
import com.appttude.h_mal.monoWeather.robot.weatherScreen
import org.junit.Test
class SettingsPageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
override fun beforeLaunch() {
stubEndpoint(baseUrl, Stubs.Metric)
clearPrefs()
}
@Test
fun loadApp_changeToImperial_returnsValidPage() {
weatherScreen {
isDisplayed()
verifyCurrentTemperature(13)
verifyCurrentLocation("Mock Location")
stubEndpoint(baseUrl, Stubs.Imperial)
openMenuItem()
}
settingsScreen {
selectWeatherUnits(UnitType.IMPERIAL)
goBack()
}
weatherScreen {
isDisplayed()
refresh()
verifyCurrentTemperature(56)
verifyCurrentLocation("Mock Location")
}
}
}

View File

@@ -15,7 +15,7 @@ import org.junit.Test
class WorldPageUITest : BaseTest<MainActivity>(MainActivity::class.java) { class WorldPageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
override fun beforeLaunch() { override fun beforeLaunch() {
stubEndpoint(baseUrl, Stubs.New) stubEndpoint(baseUrl, Stubs.Metric)
stubLocation("London", 51.5064,-0.12721) stubLocation("London", 51.5064,-0.12721)
} }
@@ -28,9 +28,8 @@ class WorldPageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
clickFab() clickFab()
} }
addLocation { addLocation {
// Todo: change this stubEndpoint(baseUrl, Stubs.Sydney)
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Sydney) stubLocation("Sydney",-33.8696,151.207)
stubLocation("Sydney", -33.89, -151.12)
setLocation("Sydney") setLocation("Sydney")
submit() submit()
} }
@@ -39,7 +38,7 @@ class WorldPageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
} }
weatherScreen { weatherScreen {
isDisplayed() isDisplayed()
verifyCurrentTemperature(12) verifyCurrentTemperature(16)
verifyCurrentLocation("Sydney") verifyCurrentLocation("Sydney")
} }
} }

View File

@@ -3,7 +3,7 @@ package com.appttude.h_mal.atlas_weather.application
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
import com.appttude.h_mal.atlas_weather.data.location.LocationProviderImpl import com.appttude.h_mal.atlas_weather.data.location.LocationProviderImpl
import com.appttude.h_mal.atlas_weather.data.network.NetworkModule import com.appttude.h_mal.atlas_weather.data.network.NetworkModule
import com.appttude.h_mal.atlas_weather.data.network.NewWeatherApi import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkConnectionInterceptor 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.interceptors.QueryParamsInterceptor
import com.appttude.h_mal.atlas_weather.data.network.networkUtils.loggingInterceptor import com.appttude.h_mal.atlas_weather.data.network.networkUtils.loggingInterceptor
@@ -11,12 +11,12 @@ import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
open class AppClass : BaseAppClass() { open class AppClass : BaseAppClass() {
override fun createNetworkModule(): NewWeatherApi { override fun createNetworkModule(): WeatherApi {
return NetworkModule().invoke<NewWeatherApi>( return NetworkModule().invoke<WeatherApi>(
NetworkConnectionInterceptor(this), NetworkConnectionInterceptor(this),
QueryParamsInterceptor(), QueryParamsInterceptor(),
loggingInterceptor loggingInterceptor
) as NewWeatherApi ) as WeatherApi
} }
override fun createLocationModule(): LocationProvider = LocationProviderImpl(this) override fun createLocationModule(): LocationProvider = LocationProviderImpl(this)

View File

@@ -4,6 +4,7 @@ import android.app.Application
import com.appttude.h_mal.atlas_weather.data.WeatherSource import com.appttude.h_mal.atlas_weather.data.WeatherSource
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
import com.appttude.h_mal.atlas_weather.data.network.Api import com.appttude.h_mal.atlas_weather.data.network.Api
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
import com.appttude.h_mal.atlas_weather.data.repository.RepositoryImpl import com.appttude.h_mal.atlas_weather.data.repository.RepositoryImpl
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepositoryImpl import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepositoryImpl
@@ -28,7 +29,7 @@ abstract class BaseAppClass : Application(), KodeinAware {
val parentModule = Kodein.Module("Parent Module", allowSilentOverride = true) { val parentModule = Kodein.Module("Parent Module", allowSilentOverride = true) {
import(androidXModule(this@BaseAppClass)) import(androidXModule(this@BaseAppClass))
bind() from singleton { createNetworkModule() } bind() from singleton { createNetworkModule() as WeatherApi }
bind() from singleton { createLocationModule() } bind() from singleton { createLocationModule() }
bind() from singleton { Gson() } bind() from singleton { Gson() }

View File

@@ -1,9 +1,7 @@
package com.appttude.h_mal.atlas_weather.data.network package com.appttude.h_mal.atlas_weather.data.network
import org.json.JSONException import retrofit2.HttpException
import org.json.JSONObject
import retrofit2.Response import retrofit2.Response
import java.io.IOException
abstract class ResponseUnwrap { abstract class ResponseUnwrap {
@@ -15,18 +13,7 @@ abstract class ResponseUnwrap {
if (response.isSuccessful) { if (response.isSuccessful) {
return response.body()!! return response.body()!!
} else { } else {
val error = response.errorBody()?.string() throw HttpException(response)
val errorMessage = error?.let {
try {
JSONObject(it).getString("message")
} catch (e: JSONException) {
e.printStackTrace()
null
}
} ?: "Error Code: ${response.code()}"
throw IOException(errorMessage)
} }
} }
} }

View File

@@ -7,13 +7,13 @@ import retrofit2.http.Path
import retrofit2.http.Query import retrofit2.http.Query
interface NewWeatherApi : Api { interface WeatherApi : Api {
@GET("{location}") @GET("{location}")
suspend fun getFromApi( suspend fun getFromApi(
@Path("location") location: String,
@Query("contentType") exclude: String = "json", @Query("contentType") exclude: String = "json",
@Query("unitGroup") units: String = "uk", @Query("unitGroup") units: String = "metric"
@Path("location") location: String
): Response<WeatherApiResponse> ): Response<WeatherApiResponse>
} }

View File

@@ -0,0 +1,16 @@
package com.appttude.h_mal.atlas_weather.data.network.response.weather
import com.google.gson.annotations.SerializedName
data class Alerts(
@SerializedName("event") var event: String? = null,
@SerializedName("headline") var headline: String? = null,
@SerializedName("ends") var ends: String? = null,
@SerializedName("endsEpoch") var endsEpoch: Int? = null,
@SerializedName("onset") var onset: String? = null,
@SerializedName("onsetEpoch") var onsetEpoch: Int? = null,
@SerializedName("id") var id: String? = null,
@SerializedName("language") var language: String? = null,
@SerializedName("link") var link: String? = null,
@SerializedName("description") var description: String? = null,
)

View File

@@ -18,7 +18,7 @@ data class WeatherApiResponse(
@SerializedName("tzoffset") var tzoffset: Int? = null, @SerializedName("tzoffset") var tzoffset: Int? = null,
@SerializedName("description") var description: String? = null, @SerializedName("description") var description: String? = null,
@SerializedName("days") var days: ArrayList<Days> = arrayListOf(), @SerializedName("days") var days: ArrayList<Days> = arrayListOf(),
@SerializedName("alerts") var alerts: ArrayList<String> = arrayListOf(), @SerializedName("alerts") var alerts: ArrayList<Alerts> = arrayListOf(),
@SerializedName("currentConditions") var currentConditions: CurrentConditions? = CurrentConditions() @SerializedName("currentConditions") var currentConditions: CurrentConditions? = CurrentConditions()
): DataMapper<FullWeather> { ): DataMapper<FullWeather> {

View File

@@ -1,7 +1,7 @@
package com.appttude.h_mal.atlas_weather.data.repository package com.appttude.h_mal.atlas_weather.data.repository
import com.appttude.h_mal.atlas_weather.data.network.NewWeatherApi
import com.appttude.h_mal.atlas_weather.data.network.ResponseUnwrap import com.appttude.h_mal.atlas_weather.data.network.ResponseUnwrap
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse
import com.appttude.h_mal.atlas_weather.data.prefs.LOCATION_CONST import com.appttude.h_mal.atlas_weather.data.prefs.LOCATION_CONST
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
@@ -12,7 +12,7 @@ import com.appttude.h_mal.atlas_weather.utils.FALLBACK_TIME
class RepositoryImpl( class RepositoryImpl(
private val api: NewWeatherApi, private val api: WeatherApi,
private val db: AppDatabase, private val db: AppDatabase,
private val prefs: PreferenceProvider private val prefs: PreferenceProvider
) : Repository, ResponseUnwrap() { ) : Repository, ResponseUnwrap() {
@@ -21,7 +21,8 @@ class RepositoryImpl(
lat: String, lat: String,
long: String long: String
): WeatherApiResponse { ): WeatherApiResponse {
return responseUnwrap { api.getFromApi(location = lat + long) } val unit = if (prefs.getUnitsType() == UnitType.METRIC) "metric" else "us"
return responseUnwrap { api.getFromApi(location = "$lat,$long", units = unit) }
} }
override suspend fun saveCurrentWeatherToRoom(entityItem: EntityItem) { override suspend fun saveCurrentWeatherToRoom(entityItem: EntityItem) {

View File

@@ -190,7 +190,7 @@ class ServicesHelper(
val list = mutableListOf<InnerWidgetCellData>() val list = mutableListOf<InnerWidgetCellData>()
result.weather.daily?.drop(1)?.dropLast(2)?.forEach { dailyWeather -> result.weather.daily?.drop(1)?.dropLast(1)?.forEach { dailyWeather ->
val day = dailyWeather.dt?.toSmallDayName() val day = dailyWeather.dt?.toSmallDayName()
val icon = dailyWeather.icon val icon = dailyWeather.icon
val temp = dailyWeather.max?.toInt().toString() val temp = dailyWeather.max?.toInt().toString()
@@ -220,7 +220,7 @@ class ServicesHelper(
val list = mutableListOf<InnerWidgetCellData>() val list = mutableListOf<InnerWidgetCellData>()
result.weather.daily?.drop(1)?.dropLast(2)?.forEach { dailyWeather -> result.weather.daily?.drop(1)?.dropLast(1)?.forEach { dailyWeather ->
val day = dailyWeather.dt?.toSmallDayName() val day = dailyWeather.dt?.toSmallDayName()
val icon = dailyWeather.icon val icon = dailyWeather.icon
val temp = dailyWeather.max?.toInt().toString() val temp = dailyWeather.max?.toInt().toString()

View File

@@ -1,6 +1,6 @@
package com.appttude.h_mal.atlas_weather.data.repository package com.appttude.h_mal.atlas_weather.data.repository
import com.appttude.h_mal.atlas_weather.data.network.NewWeatherApi import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse
import com.appttude.h_mal.atlas_weather.data.prefs.LOCATION_CONST import com.appttude.h_mal.atlas_weather.data.prefs.LOCATION_CONST
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
@@ -18,7 +18,8 @@ import kotlinx.coroutines.runBlocking
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.ArgumentMatchers.anyDouble import org.mockito.ArgumentMatchers.anyDouble
import java.io.IOException import org.mockito.ArgumentMatchers.anyString
import retrofit2.HttpException
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertIs import kotlin.test.assertIs
@@ -30,7 +31,7 @@ class RepositoryImplTest : BaseTest() {
lateinit var repository: RepositoryImpl lateinit var repository: RepositoryImpl
@MockK lateinit var api: NewWeatherApi @MockK lateinit var api: WeatherApi
@MockK @MockK
lateinit var db: AppDatabase lateinit var db: AppDatabase
@@ -93,7 +94,7 @@ class RepositoryImplTest : BaseTest() {
//Act //Act
//create a successful retrofit response //create a successful retrofit response
every { prefs.getUnitsType() } returns (UnitType.METRIC) every { prefs.getUnitsType() } returns (UnitType.METRIC)
coEvery { api.getFromApi(location = lat + long) }.returns(mockResponse) coEvery { api.getFromApi(location = "$lat,$long") }.returns(mockResponse)
// Assert // Assert
runBlocking { runBlocking {
@@ -105,20 +106,25 @@ class RepositoryImplTest : BaseTest() {
@Test @Test
fun getWeatherFromApi_validLatLong_invalidResponse() { fun getWeatherFromApi_validLatLong_invalidResponse() {
//Arrange //Arrange
val mockResponse = createErrorRetrofitMock<WeatherApiResponse>() val errorMessage = "Why dont you have a valid api key?"
val mockResponse = createErrorRetrofitMock<WeatherApiResponse>(errorMessage)
val lat = anyString()
val long = anyString()
//Act //Act
//create a successful retrofit response //create a successful retrofit response
every { prefs.getUnitsType() } returns (UnitType.METRIC) every { prefs.getUnitsType() } returns (UnitType.METRIC)
coEvery { api.getFromApi(location = any()) } returns (mockResponse) coEvery { api.getFromApi(location = "$lat,$long") } returns (mockResponse)
// Assert // Assert
val ioExceptionReturned = assertFailsWith<IOException> { val ioExceptionReturned = assertFailsWith<HttpException> {
runBlocking { runBlocking {
repository.getWeatherFromApi("", "") repository.getWeatherFromApi(lat, long)
} }
} }
assertEquals(ioExceptionReturned.message, "Error Code: 400")
assertEquals(ioExceptionReturned.code(), 400)
assertEquals(ioExceptionReturned.message(), "Why dont you have a valid api key?")
} }
@Test @Test

View File

@@ -4,7 +4,9 @@ import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.ResponseBody import okhttp3.ResponseBody
import okhttp3.ResponseBody.Companion.toResponseBody
import retrofit2.Response import retrofit2.Response
@@ -29,9 +31,17 @@ open class BaseTest {
fun <T: Any> createErrorRetrofitMock(code: Int = 400): Response<T> { fun <T: Any> createErrorRetrofitMock(code: Int = 400): Response<T> {
val responseBody = mockk<ResponseBody>(relaxed = true) val responseBody = mockk<ResponseBody>(relaxed = true)
val rawResponse = mockk<okhttp3.Response>().also {
every { it.code } returns code
}
return Response.error<T>(code, responseBody) return Response.error<T>(code, responseBody)
} }
fun <T: Any> createErrorRetrofitMock(errorMessage: String, code: Int = 400): Response<T> {
val responseBody = errorMessage.toResponseBody("application/json".toMediaType())
val rawResponse = mockk<okhttp3.Response>(relaxed = true).also {
every { it.code } returns code
every { it.isSuccessful } returns false
every { it.body } returns responseBody
every { it.message } returns errorMessage
}
return Response.error<T>(responseBody, rawResponse)
}
} }

View File

@@ -40,7 +40,7 @@ RETROFIT_VERSION = 2.9.0
OKHTTP_VERSION = 4.9.0 OKHTTP_VERSION = 4.9.0
MOKITO_INLINE_VERSION = 2.13.0 MOKITO_INLINE_VERSION = 2.13.0
CORE_TEST_VERSION = 2.2.0 CORE_TEST_VERSION = 2.2.0
MOCKK_VERSION = 1.10.5 MOCKK_VERSION = 1.13.12
TEST_JUNIT_VERSION = 1.2.0 TEST_JUNIT_VERSION = 1.2.0
TEST_RUNNER_VERSION = 1.5.2 TEST_RUNNER_VERSION = 1.5.2
ESPRESSO_VERSION = 3.6.0 ESPRESSO_VERSION = 3.6.0