From ba66ca855cd65126ce1cf176a3fd64a4cd73d6b2 Mon Sep 17 00:00:00 2001 From: Anthony FAUCOGNEY Date: Fri, 21 Jun 2019 15:00:48 +0200 Subject: [PATCH] Update projet with latest dep version + mockk Update Gradle plugin Update Android Target and co Migrate EasyMock to Mockk Fix some issue in test case Migrate to Androidx Migrate to TP 2.x (remove registries) Add https to run API with Android 9 --- .idea/caches/build_file_checksums.ser | Bin 0 -> 499 bytes .idea/encodings.xml | 4 + .idea/misc.xml | 17 ++- .idea/modules.xml | 2 + README.md | 4 +- app/build.gradle | 52 +++++---- .../weatherappkotlin/WeatherApplication.kt | 12 +-- .../location/LocationPermissionManager.kt | 4 +- .../model/location/UserLocationManager.kt | 2 +- .../model/networking/WeatherService.kt | 2 +- .../com/weatherappkotlin/view/ErrorDialog.kt | 14 +-- .../com/weatherappkotlin/view/Landing.kt | 27 +++-- .../ekamp/com/weatherappkotlin/view/Splash.kt | 9 +- .../model/NetworkStatusUtilTest.kt | 37 ++++--- .../presenter/LandingPresenterTest.kt | 101 +++++++++++------- .../presenter/SplashPresenterTest.kt | 76 +++++++------ build.gradle | 7 +- gradle.properties | 12 ++- gradle/wrapper/gradle-wrapper.properties | 4 +- 19 files changed, 211 insertions(+), 175 deletions(-) create mode 100644 .idea/caches/build_file_checksums.ser create mode 100644 .idea/encodings.xml diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser new file mode 100644 index 0000000000000000000000000000000000000000..8d16675a94e04394c4d3cec20b31dc8cdcffdb7b GIT binary patch literal 499 zcmZ4UmVvdnh`~NNKUXg?FQq6yGexf?KR>5fFEb@IQ7^qHF(oHeub?PDD>b=9F91S2 zm1gFoxMk*~I%lLNXBU^|7Q2L-Ts|(GuF1r}uGBYr_F>vMNC#JY1CYR(Fc`|U8WE7SU)i> zu{1e9JukIVA8MaIA}}$`xgy(hv!7uT%gYAN8E<|#)s-*^;!)kj5T)=#{f6Go$mH;o neYYBbPE0H)&`$zI3(RwHCo<%hh=1IW^z5{xd{q1NjL-@IbZD(0 literal 0 HcmV?d00001 diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..15a15b2 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 3963879..5745957 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -5,26 +5,37 @@ - + diff --git a/.idea/modules.xml b/.idea/modules.xml index 5d0b7d5..9bd7a7e 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,9 +2,11 @@ + + \ No newline at end of file diff --git a/README.md b/README.md index ceeae4f..aaf3901 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ https://home.openweathermap.org/api_keys ### Tools / Libs Utilized - Jetbrains Kotlin : https://kotlinlang.org/ -- Toothpick Dependency Injection : https://github.com/stephanenicolas/toothpick +- Toothpick Dependency Injection 2.1.0 : https://github.com/stephanenicolas/toothpick - RxJava 2 / RxAndroid : https://github.com/ReactiveX/RxJava - Retrofit 2 : http://square.github.io/retrofit/ - Picasso : http://square.github.io/picasso/ -- EasyMock : http://easymock.org/ +- Mockk : https://github.com/mockk/mockk diff --git a/app/build.gradle b/app/build.gradle index c2dd176..5dd4237 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,17 +1,18 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-kapt' android { - compileSdkVersion 25 - buildToolsVersion "25.0.0" + compileSdkVersion 28 + buildToolsVersion "28.0.3" defaultConfig { applicationId "weather.ekamp.com.weatherappkotlin" minSdkVersion 23 - targetSdkVersion 25 + targetSdkVersion 28 versionCode 1 versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { @@ -32,33 +33,28 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile "com.android.support:appcompat-v7:$SUPPORT_LIBS_VERSION" - compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" - compile "com.squareup.picasso:picasso:$PICASSO_VERSION" - compile "com.squareup.retrofit2:retrofit:$RETROFIT_VERSION" - compile "com.squareup.retrofit2:converter-moshi:$RETROFIT_VERSION" - compile "com.squareup.retrofit2:adapter-rxjava2:$RETROFIT_VERSION" - compile "io.reactivex.rxjava2:rxjava:$RX_JAVA_VERSION" - compile "io.reactivex.rxjava2:rxandroid:$RX_ANDROID_VERSION" - compile "com.google.android.gms:play-services-location:$PLAY_SERVICES_VERSION" - compile "com.github.stephanenicolas.toothpick:toothpick-runtime:$TOOTHPICK_VERSION" - compile "com.github.stephanenicolas.toothpick:smoothie:$TOOTHPICK_VERSION" - kapt "com.github.stephanenicolas.toothpick:toothpick-compiler:$TOOTHPICK_VERSION" - testCompile "com.github.stephanenicolas.toothpick:toothpick-testing:$TOOTHPICK_VERSION" - testCompile "junit:junit:$JUNIT_VERSION" - testCompile "org.easymock:easymock:$EASYMOCK_VERSION" + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation "androidx.appcompat:appcompat:1.0.2" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.31" + implementation "com.squareup.picasso:picasso:2.71828" + implementation "com.squareup.retrofit2:retrofit:2.6.0" + implementation "com.squareup.retrofit2:converter-moshi:2.6.0" + implementation "com.squareup.retrofit2:adapter-rxjava2:2.6.0" + implementation "io.reactivex.rxjava2:rxjava:2.2.10" + implementation "io.reactivex.rxjava2:rxandroid:2.1.1" + implementation "com.google.android.gms:play-services-location:17.0.0" + + implementation 'com.github.stephanenicolas.toothpick:toothpick-runtime:2.1.0' + implementation 'com.github.stephanenicolas.toothpick:smoothie:2.1.0' + implementation "com.github.stephanenicolas.toothpick:smoothie-androidx:2.1.0" + kapt "com.github.stephanenicolas.toothpick:toothpick-compiler:2.1.0" + testImplementation "com.github.stephanenicolas.toothpick:toothpick-testing-junit4:2.1.0" + testImplementation "junit:junit:4.12" + testImplementation "io.mockk:mockk:1.9.3" } kapt { - generateStubs = true - arguments { arg("toothpick_registry_package_name", "weather.ekamp.com.weatherappkotlin") - arg("toothpick_registry_children_package_names", "toothpick.smoothie") } -} - -repositories { - mavenCentral() -} +} \ No newline at end of file diff --git a/app/src/main/java/weather/ekamp/com/weatherappkotlin/WeatherApplication.kt b/app/src/main/java/weather/ekamp/com/weatherappkotlin/WeatherApplication.kt index 622cc7d..092698f 100644 --- a/app/src/main/java/weather/ekamp/com/weatherappkotlin/WeatherApplication.kt +++ b/app/src/main/java/weather/ekamp/com/weatherappkotlin/WeatherApplication.kt @@ -1,23 +1,13 @@ package weather.ekamp.com.weatherappkotlin import android.app.Application -import toothpick.smoothie.module.SmoothieApplicationModule import toothpick.Toothpick +import toothpick.smoothie.module.SmoothieApplicationModule import weather.ekamp.com.weatherappkotlin.model.inject.WeatherApplicationModule -import toothpick.configuration.Configuration.forDevelopment -import toothpick.configuration.Configuration.forProduction -import toothpick.registries.FactoryRegistryLocator -import toothpick.registries.MemberInjectorRegistryLocator -import toothpick.Toothpick.setConfiguration - class WeatherApplication : Application() { override fun onCreate() { super.onCreate() - val configuration = if (BuildConfig.DEBUG) forDevelopment() else forProduction() - setConfiguration(configuration.disableReflection()) - FactoryRegistryLocator.setRootRegistry(FactoryRegistry()) - MemberInjectorRegistryLocator.setRootRegistry(MemberInjectorRegistry()) val appScope = Toothpick.openScope(this) appScope.installModules(SmoothieApplicationModule(this), WeatherApplicationModule(this)) diff --git a/app/src/main/java/weather/ekamp/com/weatherappkotlin/model/location/LocationPermissionManager.kt b/app/src/main/java/weather/ekamp/com/weatherappkotlin/model/location/LocationPermissionManager.kt index 25acd56..72fe61a 100644 --- a/app/src/main/java/weather/ekamp/com/weatherappkotlin/model/location/LocationPermissionManager.kt +++ b/app/src/main/java/weather/ekamp/com/weatherappkotlin/model/location/LocationPermissionManager.kt @@ -4,8 +4,8 @@ import android.Manifest import android.app.Activity import android.app.Application import android.content.pm.PackageManager -import android.support.v4.app.ActivityCompat -import android.support.v4.content.ContextCompat +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import javax.inject.Inject import javax.inject.Singleton diff --git a/app/src/main/java/weather/ekamp/com/weatherappkotlin/model/location/UserLocationManager.kt b/app/src/main/java/weather/ekamp/com/weatherappkotlin/model/location/UserLocationManager.kt index 8f5ab59..b1d35f7 100644 --- a/app/src/main/java/weather/ekamp/com/weatherappkotlin/model/location/UserLocationManager.kt +++ b/app/src/main/java/weather/ekamp/com/weatherappkotlin/model/location/UserLocationManager.kt @@ -23,7 +23,7 @@ class UserLocationManager { fusedLocationClient.lastLocation .addOnCompleteListener(activity) { task -> if (task.isSuccessful && task.result != null) { - locationUpdateListener.onLocationUpdated(task.result) + locationUpdateListener.onLocationUpdated(task.result!!) } else { Log.w(TAG, "getLastLocation:exception", task.exception) locationUpdateListener.onLocationNotFound() diff --git a/app/src/main/java/weather/ekamp/com/weatherappkotlin/model/networking/WeatherService.kt b/app/src/main/java/weather/ekamp/com/weatherappkotlin/model/networking/WeatherService.kt index 748e83c..fcfa562 100644 --- a/app/src/main/java/weather/ekamp/com/weatherappkotlin/model/networking/WeatherService.kt +++ b/app/src/main/java/weather/ekamp/com/weatherappkotlin/model/networking/WeatherService.kt @@ -14,7 +14,7 @@ import javax.inject.Singleton @Singleton open class WeatherService @Inject constructor(var application: Application) { - private val BASE_URL = "http://api.openweathermap.org/data/2.5/" + private val BASE_URL = "https://api.openweathermap.org/data/2.5/" private val WEATHER_API_KEY = BuildConfig.WEATHER_API_KEY private val TEMPERATURE_UNITS = "imperial" private val sizeOfHttpCache : Long = 10 * 1024 * 1024 // 10 MiB diff --git a/app/src/main/java/weather/ekamp/com/weatherappkotlin/view/ErrorDialog.kt b/app/src/main/java/weather/ekamp/com/weatherappkotlin/view/ErrorDialog.kt index b59a83c..284382d 100644 --- a/app/src/main/java/weather/ekamp/com/weatherappkotlin/view/ErrorDialog.kt +++ b/app/src/main/java/weather/ekamp/com/weatherappkotlin/view/ErrorDialog.kt @@ -1,30 +1,30 @@ package weather.ekamp.com.weatherappkotlin.view import android.os.Bundle -import android.support.v4.app.DialogFragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import kotlinx.android.synthetic.main.error_dialog.* import weather.ekamp.com.weatherappkotlin.R -import kotlinx.android.synthetic.main.error_dialog.*; class ErrorDialog : DialogFragment() { - lateinit private var message : String + lateinit private var message: String companion object { - fun newInstance(message : String) : ErrorDialog { + fun newInstance(message: String): ErrorDialog { var newInstance = ErrorDialog() newInstance.message = message return newInstance } } - override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater?.inflate(R.layout.error_dialog, container) + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.error_dialog, container) } - override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { confirmationButton.setOnClickListener { dismiss() } diff --git a/app/src/main/java/weather/ekamp/com/weatherappkotlin/view/Landing.kt b/app/src/main/java/weather/ekamp/com/weatherappkotlin/view/Landing.kt index e019cf5..611c9e9 100644 --- a/app/src/main/java/weather/ekamp/com/weatherappkotlin/view/Landing.kt +++ b/app/src/main/java/weather/ekamp/com/weatherappkotlin/view/Landing.kt @@ -1,10 +1,12 @@ package weather.ekamp.com.weatherappkotlin.view +import android.annotation.SuppressLint import android.location.Location -import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.view.View import android.view.animation.Animation +import android.view.animation.AnimationUtils +import androidx.appcompat.app.AppCompatActivity import com.squareup.picasso.Picasso import kotlinx.android.synthetic.main.activity_main.* import toothpick.Scope @@ -14,14 +16,15 @@ import weather.ekamp.com.weatherappkotlin.model.location.UserLocationManager import weather.ekamp.com.weatherappkotlin.model.parsers.WeatherDescription import weather.ekamp.com.weatherappkotlin.presenter.LandingPresenter import javax.inject.Inject -import android.view.animation.AnimationUtils class Landing : AppCompatActivity(), LandingView { - @Inject lateinit var presenter : LandingPresenter - @Inject lateinit var userLocationManager : UserLocationManager - lateinit private var errorDialog : ErrorDialog - lateinit private var activityScope : Scope + @Inject + lateinit var presenter: LandingPresenter + @Inject + lateinit var userLocationManager: UserLocationManager + lateinit private var errorDialog: ErrorDialog + lateinit private var activityScope: Scope override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -37,18 +40,14 @@ class Landing : AppCompatActivity(), LandingView { weather_description.text = weatherDescription.getWeatherInformation().description temperature.text = weatherDescription.main.temp var weatherIconPath = weatherDescription.getIconUrlPath() - Picasso.with(this). - load(weatherIconPath). - fit(). - placeholder(R.drawable.cloud). - error(R.drawable.cloud). - into(weather_representation) + Picasso.get().load(weatherIconPath).fit().placeholder(R.drawable.cloud).error(R.drawable.cloud).into(weather_representation) } - override fun displayUserLocation(userLocationForDisplay : String) { + override fun displayUserLocation(userLocationForDisplay: String) { user_location.text = userLocationForDisplay } + @SuppressLint("ResourceType") override fun displayLoadingIndicator() { loading_indicator.visibility = View.VISIBLE weather_description.visibility = View.GONE @@ -56,7 +55,7 @@ class Landing : AppCompatActivity(), LandingView { weather_representation.visibility = View.GONE sync_button.animation?.let { sync_button.animation.repeatCount = Animation.INFINITE - } ?:run { + } ?: run { val rotation = AnimationUtils.loadAnimation(this, R.animator.sync_rotator) rotation.repeatCount = Animation.INFINITE sync_button.startAnimation(rotation) diff --git a/app/src/main/java/weather/ekamp/com/weatherappkotlin/view/Splash.kt b/app/src/main/java/weather/ekamp/com/weatherappkotlin/view/Splash.kt index 6f53f69..eb2d7b2 100644 --- a/app/src/main/java/weather/ekamp/com/weatherappkotlin/view/Splash.kt +++ b/app/src/main/java/weather/ekamp/com/weatherappkotlin/view/Splash.kt @@ -3,8 +3,8 @@ package weather.ekamp.com.weatherappkotlin.view import android.app.Activity import android.content.Intent import android.content.pm.PackageManager -import android.support.v7.app.AppCompatActivity import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity import toothpick.Scope import toothpick.Toothpick import weather.ekamp.com.weatherappkotlin.R @@ -15,10 +15,11 @@ import javax.inject.Inject class Splash : AppCompatActivity(), SplashView { val ERROR_DIALOG_TAG = "location_permission_error_dialog" - @Inject lateinit var presenter : SplashPresenter - lateinit var activityScope : Scope + @Inject + lateinit var presenter: SplashPresenter + lateinit var activityScope: Scope - lateinit var errorDialog : ErrorDialog + lateinit var errorDialog: ErrorDialog override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) activityScope = Toothpick.openScopes(application, this) diff --git a/app/src/test/java/weather/ekamp/com/weatherappkotlin/model/NetworkStatusUtilTest.kt b/app/src/test/java/weather/ekamp/com/weatherappkotlin/model/NetworkStatusUtilTest.kt index 7664952..a842a8b 100644 --- a/app/src/test/java/weather/ekamp/com/weatherappkotlin/model/NetworkStatusUtilTest.kt +++ b/app/src/test/java/weather/ekamp/com/weatherappkotlin/model/NetworkStatusUtilTest.kt @@ -4,10 +4,10 @@ import android.app.Application import android.content.Context import android.net.ConnectivityManager import android.net.NetworkInfo -import org.easymock.EasyMock.expect -import org.easymock.EasyMockRule -import org.easymock.EasyMockSupport -import org.easymock.Mock +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.InjectMockKs +import io.mockk.impl.annotations.MockK import org.hamcrest.CoreMatchers.`is` import org.hamcrest.MatcherAssert.assertThat import org.junit.Before @@ -15,24 +15,25 @@ import org.junit.Rule import org.junit.Test import toothpick.testing.ToothPickRule import weather.ekamp.com.weatherappkotlin.model.networking.NetworkStatusUtil -import javax.inject.Inject -class NetworkStatusUtilTest : EasyMockSupport() { +class NetworkStatusUtilTest { - var easyMockRule = EasyMockRule(this) + val toothpickRule = ToothPickRule(this) @Rule get - var toothpickRule = ToothPickRule(this) - @Rule get - - @Mock internal lateinit var application : Application - @Mock internal lateinit var connectivityManager : ConnectivityManager - @Mock internal lateinit var networkInfo : NetworkInfo + @MockK + internal lateinit var application: Application + @MockK + internal lateinit var connectivityManager: ConnectivityManager + @MockK + internal lateinit var networkInfo: NetworkInfo - @Inject lateinit var networkStatusUtil : NetworkStatusUtil + @InjectMockKs + lateinit var networkStatusUtil: NetworkStatusUtil @Before fun setup() { + MockKAnnotations.init(this) toothpickRule.setScopeName(application) toothpickRule.inject(this) } @@ -40,16 +41,14 @@ class NetworkStatusUtilTest : EasyMockSupport() { @Test fun test_internetConnectivityAvailable_returnsTrue_when_networkInfoIsConnectedOrConnectingReturnsTrue() { //GIVEN - expect(application.getSystemService(Context.CONNECTIVITY_SERVICE)).andReturn(connectivityManager) - expect(connectivityManager.activeNetworkInfo).andReturn(networkInfo) - expect(networkInfo.isConnectedOrConnecting).andReturn(true) - replayAll() + every { application.getSystemService(Context.CONNECTIVITY_SERVICE) } returns connectivityManager + every { connectivityManager.activeNetworkInfo } returns networkInfo + every { networkInfo.isConnectedOrConnecting } returns true //WHEN val result = networkStatusUtil.internetConnectivityAvailable() //THEN - verifyAll() assertThat(result, `is`(true)) } } diff --git a/app/src/test/java/weather/ekamp/com/weatherappkotlin/presenter/LandingPresenterTest.kt b/app/src/test/java/weather/ekamp/com/weatherappkotlin/presenter/LandingPresenterTest.kt index ac7bbf6..351043e 100644 --- a/app/src/test/java/weather/ekamp/com/weatherappkotlin/presenter/LandingPresenterTest.kt +++ b/app/src/test/java/weather/ekamp/com/weatherappkotlin/presenter/LandingPresenterTest.kt @@ -2,115 +2,138 @@ package weather.ekamp.com.weatherappkotlin.presenter import android.app.Application import android.location.Location +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.InjectMockKs +import io.mockk.impl.annotations.MockK +import io.mockk.verifySequence import io.reactivex.Single -import org.easymock.* -import org.easymock.EasyMock.expect +import io.reactivex.android.plugins.RxAndroidPlugins +import io.reactivex.schedulers.Schedulers import org.junit.Before import org.junit.Rule import org.junit.Test import toothpick.testing.ToothPickRule import weather.ekamp.com.weatherappkotlin.model.networking.WeatherService +import weather.ekamp.com.weatherappkotlin.model.parsers.Weather import weather.ekamp.com.weatherappkotlin.model.parsers.WeatherDescription import weather.ekamp.com.weatherappkotlin.view.LandingView -import javax.inject.Inject -class LandingPresenterTest : EasyMockSupport() { +class LandingPresenterTest { - var easyMockRule = EasyMockRule(this) - @Rule get - - var toothpickRule = ToothPickRule(this) - @Rule get + val toothpickRule = ToothPickRule(this) + @Rule get private val TEST_MESSAGE = "Error" private val TEST_LOCATION_LAT_NUM = 40.758895 private val TEST_LOCATION_LNG_NUM = -73.9873197 - @Mock internal lateinit var landingView : LandingView - @Mock internal lateinit var weatherService : WeatherService - @Mock internal lateinit var location : Location - @Mock internal lateinit var application : Application - @Mock internal lateinit var single : Single - @Mock internal lateinit var weatherDescription : WeatherDescription - @Mock internal lateinit var throwable : Throwable - - @Inject lateinit var landingPresenter : LandingPresenter + @MockK + internal lateinit var landingView: LandingView + @MockK + internal lateinit var weatherService: WeatherService + @MockK + internal lateinit var location: Location + @MockK + internal lateinit var application: Application + @MockK + internal lateinit var single: Single + @MockK + internal lateinit var weatherDescription: WeatherDescription + @MockK + internal lateinit var throwable: Throwable + + @InjectMockKs + lateinit var landingPresenter: LandingPresenter @Before fun setup() { + MockKAnnotations.init(this) toothpickRule.setScopeName(application) toothpickRule.inject(this) landingPresenter.landingView = landingView + RxAndroidPlugins.setInitMainThreadSchedulerHandler { _ -> Schedulers.trampoline() } } @Test fun test_onAttachView_collectsUsersLocation_and_displaysLoadingIndicator() { //GIVEN - expect(landingView.getUsersLocation()) - expect(landingView.displayLoadingIndicator()) - replayAll() + every { (landingView.getUsersLocation()) } returns Unit + every { (landingView.displayLoadingIndicator()) } returns Unit //WHEN landingPresenter.onAttachView(landingView) //THEN - verifyAll() + verifySequence { + landingView.displayLoadingIndicator() + landingView.getUsersLocation() + } } @Test fun test_createAndRegisterWeatherSubscription_createsSubscriptionToWeatherService() { //GIVEN - expect(location.latitude).andReturn(TEST_LOCATION_LAT_NUM) - expect(location.longitude).andReturn(TEST_LOCATION_LNG_NUM) - expect(weatherService.getWeatherInformation(TEST_LOCATION_LAT_NUM.toFloat().toString(), TEST_LOCATION_LNG_NUM.toFloat().toString())).andReturn(single) - replayAll() + every { (location.latitude) } returns (TEST_LOCATION_LAT_NUM) + every { (location.longitude) } returns (TEST_LOCATION_LNG_NUM) + val weatherArray: Array = arrayOf(Weather("Rennes", "Soleil", "01d.png")) + val temperatureInformation: WeatherDescription.TemperatureInformation = WeatherDescription.TemperatureInformation("15", "50", "10", "18") + val single: Single = Single.just(WeatherDescription(weatherArray, temperatureInformation)) + every { (weatherService.getWeatherInformation(TEST_LOCATION_LAT_NUM.toFloat().toString(), TEST_LOCATION_LNG_NUM.toFloat().toString())) } returns (single) //WHEN landingPresenter.createAndRegisterWeatherSubscription(location) //THEN - verifyAll() + verifySequence { + weatherService.getWeatherInformation(any(), any()) + } } @Test fun test_createLocationSubscription_getsUserLocationFromLandingView() { //GIVEN - expect(landingView.getUsersLocation()) - replayAll() + every { (landingView.getUsersLocation()) } returns Unit //WHEN landingPresenter.createLocationSubscription() //THEN - verifyAll() + verifySequence { + landingView.getUsersLocation() + } } @Test fun test_onWeatherCollected_hidesProgressIndicatorAndShowsWeatherInformation() { //GIVEN - expect(landingView.hideLoadingIndicator()) - expect(landingView.displayCurrentWeather(weatherDescription)) - replayAll() + every { (landingView.hideLoadingIndicator()) } returns Unit + every { (landingView.displayCurrentWeather(weatherDescription)) } returns Unit //WHEN landingPresenter.onWeatherCollected(weatherDescription) //THEN - verifyAll() + verifySequence { + landingView.hideLoadingIndicator() + landingView.displayCurrentWeather(weatherDescription) + } } @Test fun test_onWeatherCollectionFailure_hidesProgressIndicatorAndShowsError() { //GIVEN - expect(landingView.hideLoadingIndicator()) - expect(landingView.displayErrorToUser(TEST_MESSAGE)) - expect(throwable.localizedMessage).andReturn(TEST_MESSAGE) - replayAll() + every { (landingView.hideLoadingIndicator()) } returns Unit + every { (landingView.displayErrorToUser(TEST_MESSAGE)) } returns Unit + every { (throwable.localizedMessage) } returns (TEST_MESSAGE) //WHEN landingPresenter.onWeatherCollectionFailure(throwable) //THEN - verifyAll() + verifySequence { + landingView.hideLoadingIndicator() + landingView.displayErrorToUser(TEST_MESSAGE) + } } } diff --git a/app/src/test/java/weather/ekamp/com/weatherappkotlin/presenter/SplashPresenterTest.kt b/app/src/test/java/weather/ekamp/com/weatherappkotlin/presenter/SplashPresenterTest.kt index 7059a85..681c55e 100644 --- a/app/src/test/java/weather/ekamp/com/weatherappkotlin/presenter/SplashPresenterTest.kt +++ b/app/src/test/java/weather/ekamp/com/weatherappkotlin/presenter/SplashPresenterTest.kt @@ -2,8 +2,14 @@ package weather.ekamp.com.weatherappkotlin.presenter import android.app.Activity import android.app.Application -import org.easymock.* -import org.easymock.EasyMock.expect +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.InjectMockKs +import io.mockk.impl.annotations.MockK +import io.mockk.verifySequence +import io.reactivex.android.plugins.RxAndroidPlugins +import io.reactivex.schedulers.Schedulers +import org.hamcrest.CoreMatchers.`is` import org.junit.Assert.assertThat import org.junit.Before import org.junit.Rule @@ -11,101 +17,101 @@ import org.junit.Test import toothpick.testing.ToothPickRule import weather.ekamp.com.weatherappkotlin.model.location.LocationPermissionManager import weather.ekamp.com.weatherappkotlin.view.SplashView -import javax.inject.Inject - -import org.hamcrest.CoreMatchers.`is` - -class SplashPresenterTest : EasyMockSupport() { - val easyMockRule = EasyMockRule(this) - @Rule get +class SplashPresenterTest { val toothpickRule = ToothPickRule(this) - @Rule get + @Rule get - @Mock lateinit var splashView : SplashView - @Mock lateinit var activity : Activity - @Mock lateinit var application : Application - @Mock lateinit var locationPermissionManager : LocationPermissionManager + @MockK + lateinit var splashView: SplashView + @MockK + lateinit var activity: Activity + @MockK + lateinit var application: Application + @MockK + lateinit var locationPermissionManager: LocationPermissionManager - @Inject lateinit var splashPresenter : SplashPresenter + @InjectMockKs + lateinit var splashPresenter: SplashPresenter @Before fun setup() { + MockKAnnotations.init(this) toothpickRule.setScopeName(application) toothpickRule.inject(this) splashPresenter.splashView = splashView + RxAndroidPlugins.setInitMainThreadSchedulerHandler { _ -> Schedulers.trampoline() } } @Test fun test_onAttachView_landingIsStarted_when_locationPermissionHasBeenGranted() { //GIVEN - expect(splashView.startLanding()) - expect(locationPermissionManager.hasLocationPermissionBeenGranted()).andReturn(true) - replayAll() + every { splashView.startLanding() } returns Unit + every { locationPermissionManager.hasLocationPermissionBeenGranted() } returns true //WHEN splashPresenter.onAttachView(splashView) //THEN - verifyAll() + verifySequence { + splashView.startLanding() + } } @Test fun test_onAttachView_locationPermissionIsRequested_when_locationPermissionHasNotBeenGranted() { //GIVEN - expect(locationPermissionManager.hasLocationPermissionBeenGranted()).andReturn(false) - expect(locationPermissionManager.requestLocationPermission(activity)) - expect(splashView.getActivityReference()).andReturn(activity) - replayAll() + every { locationPermissionManager.hasLocationPermissionBeenGranted() } returns false + every { locationPermissionManager.requestLocationPermission(activity) } returns Unit + every { splashView.getActivityReference() } returns activity //WHEN splashPresenter.onAttachView(splashView) //THEN - verifyAll() + verifySequence { + splashView.getActivityReference() + } } @Test fun test_checkIfStartupComplete_returns_true_when_locationPermissionHasBeenGranted() { //GIVEN - expect(locationPermissionManager.hasLocationPermissionBeenGranted()).andReturn(true) - replayAll() + every { locationPermissionManager.hasLocationPermissionBeenGranted() } returns true //WHEN val result = splashPresenter.checkIfStartupComplete() //THEN - verifyAll() assertThat(result, `is`(true)) } @Test fun test_checkIfStartupComplete_returns_false_when_locationPermissionHasNotBeenGranted() { //GIVEN - expect(locationPermissionManager.hasLocationPermissionBeenGranted()).andReturn(false) - expect(locationPermissionManager.requestLocationPermission(activity)) - expect(splashView.getActivityReference()).andReturn(activity) - replayAll() + every { locationPermissionManager.hasLocationPermissionBeenGranted() } returns false + every { locationPermissionManager.requestLocationPermission(activity) } returns Unit + every { splashView.getActivityReference() } returns activity //WHEN val result = splashPresenter.checkIfStartupComplete() //THEN - verifyAll() assertThat(result, `is`(false)) } @Test fun test_onLocationPermissionDenied_showsRationaleToUser() { //GIVEN - expect(splashView.showLocationPermissionRationaleDialog()) - replayAll() + every { splashView.showLocationPermissionRationaleDialog() } returns Unit //WHEN splashPresenter.onLocationPermissionDenied() //THEN - verifyAll() + verifySequence { + splashView.showLocationPermissionRationaleDialog() + } } } diff --git a/build.gradle b/build.gradle index 608180a..62cf778 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,13 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.1.2-4' + ext.kotlin_version = '1.3.31' repositories { jcenter() + google() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.3' + classpath 'com.android.tools.build:gradle:3.4.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong @@ -17,6 +18,8 @@ buildscript { allprojects { repositories { jcenter() + google() + mavenCentral() } } diff --git a/gradle.properties b/gradle.properties index 72c512c..537eee0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,11 +1,13 @@ org.gradle.jvmargs=-Xmx1536m -PLAY_SERVICES_VERSION=11.0.2 -SUPPORT_LIBS_VERSION=25.3.1 -TOOTHPICK_VERSION=1.0.8 +PLAY_SERVICES_VERSION=16.0.0 +SUPPORT_LIBS_VERSION=27.1.1 +TOOTHPICK_VERSION=1.1.3 EASYMOCK_VERSION=3.4 JUNIT_VERSION=4.12 -RX_ANDROID_VERSION=2.0.1 -RX_JAVA_VERSION=2.1.0 +RX_ANDROID_VERSION=2.1.1 +RX_JAVA_VERSION=2.1.1 RETROFIT_VERSION=2.3.0 PICASSO_VERSION=2.5.2 +android.useAndroidX=true +android.enableJetifier=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 04e285f..34420ed 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Dec 28 10:00:20 PST 2015 +#Fri Jun 21 11:41:26 CEST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip