diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser new file mode 100644 index 0000000..8d16675 Binary files /dev/null and b/.idea/caches/build_file_checksums.ser differ 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