Skip to content

Commit

Permalink
Start with testing
Browse files Browse the repository at this point in the history
Add dependencies for testing, write weather use case test, swap execute function in UseCase for overloaded invoke operator, update README.md
  • Loading branch information
LukaKordic committed Jul 24, 2019
1 parent f2209d1 commit ec1a469
Show file tree
Hide file tree
Showing 13 changed files with 63 additions and 17 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ As you can see on the diagram above application's code is separated into 4 layer
* **domain** module
* **data** module

To wire it all up and to manage dependencies inside our codebase we are using Koin dependency injection framework.

#### App module
We have left default name for this one. This module contains all of the code related to the UI/Presentation layer. This includes things like Activities, Fragments and ViewModels.
We'll use MVVM arhitectural pattern to separate our UI from the rest of the app. Presentation layer is built using Android Architecture Components: ViewModels, Lifecycle and LiveData elements.
Expand All @@ -24,5 +26,7 @@ These components are also used throughout the rest of the application, thus help
Domain module contains application's business logic. This is the core part of every app and it should be clear what project is all about just by looking at these files/classes. This module should also be independent of any framework. That's why it is the innermost layer in Uncle Bob's onion diagram. Following the dependency rule this module shouldn't depend on anything from **app** or **data** module. We will hold our Use Cases and business models in this module. We'll also store repository abstractions in this module. This allows us to communicate with the rest of the application following The Dependency Inversion Principle.

#### Data module
Data module holds all concrete implementations of our repositories and other data sources like databases or network, split into corresponding packages. Every data source holds its own model classes. Before sending data further into the domain layer we are going to map it into business models. This step will help us filter out data that is not valid or may not be used further in the app.
Data module holds all concrete implementations of our repositories and other data sources like databases or network, split into corresponding packages. Every data source holds its own model classes. Every model class that is going to be saved into the database or is going to be used in the domain layer should implement DomainMapper and RoomMapper and their respective methods in order to prepare data for the next layer. This step will help us filter out data that is not valid or may not be used further in the app.
To cache our data locally we are using Google's Room Persistance Library.
We are also using Retrofit for network requests and Gson for parsing json responses.

1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:$mockitoVersion"

//di
implementation "org.koin:koin-android:$koinVersion"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class WeatherViewModel(private val weatherUseCase: GetWeatherUseCase) : ViewModel() {
class WeatherViewModel(private val getWeather: GetWeatherUseCase) : ViewModel() {

// we make this private and provide only immutable live data to observers so they can't change anything
private val _weatherLiveData = MutableLiveData<ViewState<WeatherInfo>>()
Expand All @@ -24,7 +24,7 @@ class WeatherViewModel(private val weatherUseCase: GetWeatherUseCase) : ViewMode
fun getWeatherForLocation(location: String = DEFAULT_CITY_NAME) = viewModelScope.launch {
_weatherLiveData.value = ViewState.loading()
withContext(Dispatchers.IO) {
weatherUseCase.execute(location)
getWeather(location)
.onSuccess { _weatherLiveData.postValue(ViewState.success(it)) }
.onFailure { _weatherLiveData.postValue(ViewState.error(it.throwable)) }
}
Expand Down
9 changes: 9 additions & 0 deletions app/src/test/java/com/cobeisfresh/WeatherViewModelTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.cobeisfresh

import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnitRunner

@RunWith(MockitoJUnitRunner::class)
class WeatherViewModelTest {

}
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ buildscript {
roomVersion = ROOM_VERSION
okhttp3LogInterceptor = OKHTTP3LOGINTERCEPTOR_VERSION
architectureComponents = ARHITECTURECOMPONENTS_VERSION
mockitoVersion = MOCKITO_VERSION
}
repositories {
google()
Expand Down
1 change: 1 addition & 0 deletions data/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:$mockitoVersion"

//networking
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
Expand Down
3 changes: 3 additions & 0 deletions data/src/test/java/com/example/data/TestUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.example.data

const val OSIJEK_CITY_NAME="Osijek"
1 change: 1 addition & 0 deletions domain/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:$mockitoVersion"

//coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ import com.example.domain.model.WeatherInfo

interface GetWeatherUseCase {

suspend fun execute(location: String): Result<WeatherInfo>
suspend operator fun invoke(location: String): Result<WeatherInfo>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import com.example.domain.repository.WeatherRepository

class GetWeatherUseCaseImpl(private val weatherRepository: WeatherRepository) : GetWeatherUseCase {

override suspend fun execute(location: String) = weatherRepository.getWeatherForLocation(location)
override suspend operator fun invoke(location: String) = weatherRepository.getWeatherForLocation(location)
}
3 changes: 3 additions & 0 deletions domain/src/test/java/com/example/domain/TestUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.example.domain

const val OSIJEK_CITY_NAME = "Osijek"
27 changes: 27 additions & 0 deletions domain/src/test/java/com/example/domain/WeatherUseCaseTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.example.domain

import com.example.domain.interaction.weather.GetWeatherUseCaseImpl
import com.example.domain.repository.WeatherRepository
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyBlocking
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.junit.MockitoJUnitRunner

@RunWith(MockitoJUnitRunner::class)
class WeatherUseCaseTest {

private val weatherRepository: WeatherRepository = mock()
private val getWeather by lazy { GetWeatherUseCaseImpl(weatherRepository) }

@Test
fun `test GetWeatherUseCase calls WeatherRepository`() {
runBlocking {
getWeather(OSIJEK_CITY_NAME)
verify(weatherRepository).getWeatherForLocation(OSIJEK_CITY_NAME)
}
}
}
20 changes: 8 additions & 12 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,26 @@ android.useAndroidX=true
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official


##################
### VERSIONING ###
##################

# APP VERSION
VERSION_NAME=1.0.0
VERSION_CODE=1

# ANDROID BUILD SETTINGS
ANDROID_MIN_SDK_VERSION=21
ANDROID_TARGET_SDK_VERSION=28
ANDROID_COMPILE_SDK_VERSION=28
ANDROID_BUILD_TOOLS_VERSION=28.0.3

# LIBRARIES
KOTLIN_VERSION = 1.3.41
COROUTINES_VERSION = 1.2.1
KOIN_VERSION = 2.0.1
RETROFIT_VERSION = 2.6.0
ROOM_VERSION = 2.1.0
OKHTTP3LOGINTERCEPTOR_VERSION= 3.10.0
ARHITECTURECOMPONENTS_VERSION = 2.2.0-alpha02
KOTLIN_VERSION=1.3.41
COROUTINES_VERSION=1.2.1
KOIN_VERSION=2.0.1
RETROFIT_VERSION=2.6.0
ROOM_VERSION=2.1.0
OKHTTP3LOGINTERCEPTOR_VERSION=3.10.0
ARHITECTURECOMPONENTS_VERSION=2.2.0-alpha02
MOCKITO_VERSION=2.1.0



0 comments on commit ec1a469

Please sign in to comment.