Merge pull request #9 from hakodeveloper/structure/testing

Create Tests
This commit is contained in:
Carlos Martinez
2020-02-06 14:56:32 -03:00
committed by GitHub
24 changed files with 292 additions and 25 deletions

View File

@@ -3,4 +3,6 @@ apply from: '../core.gradle'
dependencies { dependencies {
implementation project(':base') implementation project(':base')
testImplementation project(':testing')
androidTestImplementation project(':testing')
} }

View File

@@ -37,6 +37,10 @@ android {
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
testOptions { testOptions {
unitTests.returnDefaultValues = true unitTests.returnDefaultValues = true
} }
@@ -51,4 +55,6 @@ dependencies {
implementation project(":userlist") implementation project(":userlist")
implementation project(":albumlist") implementation project(":albumlist")
implementation project(":photolist") implementation project(":photolist")
testImplementation project(':testing')
androidTestImplementation project(':testing')
} }

View File

@@ -1,10 +1,8 @@
package com.hako.friendlists package com.hako.friendlists
import android.app.Application import android.app.Application
import com.hako.albumlist.di.albumListModules
import com.hako.userlist.di.userlistModules import com.hako.userlist.di.userlistModules
import com.hako.friendlists.di.appModules import com.hako.friendlists.di.appModules
import com.hako.photolist.di.photoListModules
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
import timber.log.Timber import timber.log.Timber
@@ -28,9 +26,7 @@ class MainApplication : Application() {
modules( modules(
listOf( listOf(
appModules, appModules,
userlistModules, userlistModules
albumListModules,
photoListModules
) )
) )
} }

View File

@@ -4,19 +4,35 @@ import android.os.Bundle
import androidx.annotation.IdRes import androidx.annotation.IdRes
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.hako.albumlist.di.albumListModules
import com.hako.albumlist.feature.ALBUMLIST_FRAGMENT_BUNDLE_USER_ID import com.hako.albumlist.feature.ALBUMLIST_FRAGMENT_BUNDLE_USER_ID
import com.hako.albumlist.navigation.AlbumlistNavigation import com.hako.albumlist.navigation.AlbumlistNavigation
import com.hako.base.extensions.buildNavigation import com.hako.base.extensions.buildNavigation
import com.hako.base.navigation.NavigationEvent import com.hako.base.navigation.NavigationEvent
import com.hako.friendlists.R import com.hako.friendlists.R
import com.hako.photolist.di.photoListModules
import com.hako.photolist.feature.PHOTOLIST_FRAGMENT_BUNDLE_ALBUM_ID import com.hako.photolist.feature.PHOTOLIST_FRAGMENT_BUNDLE_ALBUM_ID
import com.hako.userlist.navigation.UserlistNavigation import com.hako.userlist.navigation.UserlistNavigation
import org.koin.core.context.loadKoinModules
// This sets the fragment title, it's referenced in every navigation // This sets the fragment title, it's referenced in every navigation
const val FRAGMENT_TITLE = "actionTitle" const val FRAGMENT_TITLE = "actionTitle"
class NavigationViewmodel : ViewModel() { class NavigationViewmodel : ViewModel() {
// Load koin modules dynamically ;)
private val albums by lazy {
loadKoinModules(albumListModules)
}
private val photos by lazy {
loadKoinModules(photoListModules)
}
private fun injectAlbums() = albums
private fun injectPhotos() = photos
val navigate = MutableLiveData<Pair<@IdRes Int, Bundle>>() val navigate = MutableLiveData<Pair<@IdRes Int, Bundle>>()
fun onNavigationEvent(event: NavigationEvent) { fun onNavigationEvent(event: NavigationEvent) {
@@ -28,6 +44,8 @@ class NavigationViewmodel : ViewModel() {
} }
private fun handleUserlistNavigation(event: UserlistNavigation) { private fun handleUserlistNavigation(event: UserlistNavigation) {
injectAlbums()
when (event) { when (event) {
is UserlistNavigation.ClickedOnUser -> navigate.postValue( is UserlistNavigation.ClickedOnUser -> navigate.postValue(
buildNavigation(R.id.action_userlistFragment_to_albumlistFragment, Bundle().apply { buildNavigation(R.id.action_userlistFragment_to_albumlistFragment, Bundle().apply {
@@ -42,6 +60,8 @@ class NavigationViewmodel : ViewModel() {
} }
private fun handleAlbumlistNavigation(event: AlbumlistNavigation) { private fun handleAlbumlistNavigation(event: AlbumlistNavigation) {
injectPhotos()
when (event) { when (event) {
is AlbumlistNavigation.ClickedOnAlbum -> navigate.postValue( is AlbumlistNavigation.ClickedOnAlbum -> navigate.postValue(
buildNavigation(R.id.action_albumlistFragment_to_photolistFragment, Bundle().apply { buildNavigation(R.id.action_albumlistFragment_to_photolistFragment, Bundle().apply {

View File

@@ -52,12 +52,6 @@ dependencies {
api deps.timber api deps.timber
api deps.lottie api deps.lottie
api deps.picasso api deps.picasso
//Testing testImplementation project(':testing')
api deps.testing.junit androidTestImplementation project(':testing')
api deps.testing.koin
api deps.testing.core
api deps.testing.rules
api deps.testing.runner
api deps.testing.ext
api deps.testing.espresso
} }

View File

@@ -15,9 +15,6 @@ interface AlbumDao {
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun saveAll(entities: List<AlbumEntity>) fun saveAll(entities: List<AlbumEntity>)
@get:Query("SELECT * FROM ${AlbumEntity.TABLE_NAME}")
val all: List<AlbumEntity>
@Query("SELECT * FROM ${AlbumEntity.TABLE_NAME} WHERE userId = :userId ORDER BY id ASC") @Query("SELECT * FROM ${AlbumEntity.TABLE_NAME} WHERE userId = :userId ORDER BY id ASC")
fun getAlbums(userId: Int): List<AlbumEntity> fun getAlbums(userId: Int): List<AlbumEntity>

View File

@@ -15,9 +15,6 @@ interface PhotoDao {
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun saveAll(entities: List<PhotoEntity>) fun saveAll(entities: List<PhotoEntity>)
@get:Query("SELECT * FROM ${PhotoEntity.TABLE_NAME}")
val all: List<PhotoEntity>
@Query("SELECT * FROM ${PhotoEntity.TABLE_NAME} WHERE albumId = :albumId ORDER BY id ASC") @Query("SELECT * FROM ${PhotoEntity.TABLE_NAME} WHERE albumId = :albumId ORDER BY id ASC")
fun getPhotos(albumId: Int): List<PhotoEntity> fun getPhotos(albumId: Int): List<PhotoEntity>

View File

@@ -18,9 +18,6 @@ interface UserDao {
@Query("UPDATE ${UserEntity.TABLE_NAME} SET isFavorite = :favorite WHERE id = :id") @Query("UPDATE ${UserEntity.TABLE_NAME} SET isFavorite = :favorite WHERE id = :id")
fun saveFavorite(id: Int, favorite: Boolean): Int fun saveFavorite(id: Int, favorite: Boolean): Int
@get:Query("SELECT * FROM ${UserEntity.TABLE_NAME}")
val all: List<UserEntity>
@Query("SELECT * FROM ${UserEntity.TABLE_NAME} ORDER BY id ASC") @Query("SELECT * FROM ${UserEntity.TABLE_NAME} ORDER BY id ASC")
fun getAllUsers(): List<UserEntity> fun getAllUsers(): List<UserEntity>

View File

@@ -13,7 +13,7 @@ data class UserEntity(
val email: String, val email: String,
val phone: String, val phone: String,
val website: String, val website: String,
val isFavorite: Boolean = true val isFavorite: Boolean = false
) { ) {
companion object { companion object {
const val TABLE_NAME = "users" const val TABLE_NAME = "users"

View File

@@ -19,6 +19,10 @@ android {
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
testOptions { testOptions {
unitTests.returnDefaultValues = true unitTests.returnDefaultValues = true
} }

View File

@@ -3,4 +3,6 @@ apply from: '../core.gradle'
dependencies { dependencies {
implementation project(':base') implementation project(':base')
testImplementation project(':testing')
androidTestImplementation project(':testing')
} }

View File

@@ -1,2 +1,2 @@
include ':app', ':base', ':userlist', ':albumlist', ':photolist' include ':app', ':base', ':userlist', ':albumlist', ':photolist', ':testing'
rootProject.name='Friendlists' rootProject.name='Friendlists'

1
testing/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

14
testing/build.gradle Normal file
View File

@@ -0,0 +1,14 @@
apply plugin: 'com.android.library'
apply from: '../core.gradle'
apply from: 'versions.gradle'
dependencies {
api deps.testing.junit
api deps.testing.koin
api deps.testing.core
api deps.testing.rules
api deps.testing.runner
api deps.testing.ext
api deps.testing.espresso
api deps.testing.fragments
}

View File

21
testing/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hako.testing" />

View File

@@ -0,0 +1,10 @@
package com.hako.testing
import androidx.annotation.RestrictTo
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.espresso.assertion.ViewAssertions.matches
@RestrictTo(RestrictTo.Scope.TESTS)
fun String.isTextDisplayed() = onView(withText(this)).check(matches(ViewMatchers.isDisplayed()))

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">testing</string>
</resources>

24
testing/versions.gradle Normal file
View File

@@ -0,0 +1,24 @@
ext.deps = [:]
def versions = [:]
versions.koin = "2.0.1"
versions.junit = "4.13"
versions.test = "1.2.0"
versions.test_ext = "1.1.1"
versions.espresso = "3.2.0"
versions.fragments = "1.2.0"
def deps = [:]
def testing = [:]
testing.junit = "junit:junit:$versions.junit"
testing.core = "androidx.test:core:$versions.test"
testing.rules = "androidx.test:rules:$versions.test"
testing.runner = "androidx.test:runner:$versions.test"
testing.ext = "androidx.test.ext:junit:$versions.test_ext"
testing.koin = "org.koin:koin-test:$versions.koin"
testing.espresso = "androidx.test.espresso:espresso-core:$versions.espresso"
testing.fragments = "androidx.fragment:fragment-testing:$versions.fragments"
deps.testing = testing
ext.deps = deps

View File

@@ -108,7 +108,7 @@ empty-blocks:
EmptyForBlock: EmptyForBlock:
active: true active: true
EmptyFunctionBlock: EmptyFunctionBlock:
active: true active: false
ignoreOverridden: false ignoreOverridden: false
EmptyIfBlock: EmptyIfBlock:
active: true active: true

View File

@@ -3,4 +3,6 @@ apply from: '../core.gradle'
dependencies { dependencies {
implementation project(':base') implementation project(':base')
testImplementation project(':testing')
androidTestImplementation project(':testing')
} }

View File

@@ -0,0 +1,29 @@
package com.hako.userlist.feature
import com.hako.base.domain.database.dao.UserDao
import com.hako.base.domain.database.entities.UserEntity
import com.hako.userlist.domain.datasource.UserlistRemoteApi
import com.hako.userlist.model.User
import io.reactivex.Single
class MockUserDao(private val userList: List<UserEntity>) : UserDao {
override fun save(entity: UserEntity) {}
override fun saveAll(entities: List<UserEntity>) {}
override fun saveFavorite(id: Int, favorite: Boolean) = 1
override fun getAllUsers() = userList
override fun getFavoriteUsers() = userList.filter { it.isFavorite }
override fun count() = userList.count()
override fun nukeDatabase() {}
}
class MockUserApi(private val userList: List<User>) : UserlistRemoteApi {
override fun getUsers() = Single.fromCallable { getAllUsers() }
private fun getAllUsers() = userList
}

View File

@@ -0,0 +1,146 @@
package com.hako.userlist.feature
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.platform.app.InstrumentationRegistry
import com.hako.base.domain.database.dao.UserDao
import com.hako.base.domain.database.entities.UserEntity
import com.hako.testing.isTextDisplayed
import com.hako.userlist.domain.datasource.UserlistRemoteApi
import com.hako.userlist.domain.usecase.GetFavoriteUsers
import com.hako.userlist.domain.usecase.GetUsers
import com.hako.userlist.domain.usecase.SetFavoriteStatus
import com.hako.userlist.model.User
import com.hako.userlist.model.toUserEntity
import com.hako.userlist.viewmodel.UserlistViewmodel
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.core.context.loadKoinModules
import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin
import org.koin.dsl.module
class UserlistFragmentTest {
@Before
fun setupKoin() {
startKoin {
InstrumentationRegistry.getInstrumentation().targetContext
modules(module {
factory { GetUsers(get()) }
factory { GetFavoriteUsers(get()) }
factory { SetFavoriteStatus(get()) }
viewModel { UserlistViewmodel() }
})
}
}
@After
fun killKoin() {
stopKoin()
}
@Test
fun shouldShowUserlist_withAllUsers() {
userlist {
withTwoBasicUsers()
} should {
showTwoBasicUsers()
}
}
@Test
fun shouldShowOnlyFavoriteUserlist_withAllUsers() {
userlist {
withTwoBasicOneFavoriteUsers()
} should {
showOnlyOneFavorite()
}
}
private fun userlist(func: UserlistRobot.() -> Unit) =
UserlistRobot().apply {
func()
}
}
class UserlistRobot {
fun withTwoBasicUsers() {
loadKoinModules(
module {
factory<UserDao> { MockUserDao(loadTwoBasicUsers().map { it.toUserEntity() }) }
factory<UserlistRemoteApi> { MockUserApi(loadTwoBasicUsers()) }
}
)
launchFragmentInContainer<UserlistFragment>()
}
fun withTwoBasicOneFavoriteUsers() {
loadKoinModules(
module {
factory<UserDao> { MockUserDao(loadTwoUsersOneFavorite()) }
factory<UserlistRemoteApi> { MockUserApi(loadTwoBasicUsers()) }
}
)
launchFragmentInContainer<FavoriteUserlistFragment>()
}
infix fun should(func: UserlistResult.() -> Unit) {
UserlistResult().apply { func() }
}
private fun loadTwoBasicUsers() = listOf(
User(
1,
"Marian Arriaga",
"mariancita",
"test@gmail.com",
"+56873912",
"www.test.com"
),
User(
2,
"Carlos Martinez",
"carlitos",
"test2@gmail.com",
"+56873912",
"www.test2.com"
)
)
private fun loadTwoUsersOneFavorite() = listOf(
UserEntity(
1,
"Marian Arriaga",
"mariancita",
"test@gmail.com",
"+56873912",
"www.test.com",
true
),
UserEntity(
2,
"Carlos Martinez",
"carlitos",
"test2@gmail.com",
"+56873912",
"www.test2.com"
)
)
}
class UserlistResult {
fun showTwoBasicUsers() {
"Marian Arriaga".isTextDisplayed()
"mariancita".isTextDisplayed()
"Carlos Martinez".isTextDisplayed()
"carlitos".isTextDisplayed()
}
fun showOnlyOneFavorite() {
"Marian Arriaga".isTextDisplayed()
"mariancita".isTextDisplayed()
}
}