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 {
implementation project(':base')
testImplementation project(':testing')
androidTestImplementation project(':testing')
}

View File

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

View File

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

View File

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

View File

@@ -52,12 +52,6 @@ dependencies {
api deps.timber
api deps.lottie
api deps.picasso
//Testing
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
testImplementation project(':testing')
androidTestImplementation project(':testing')
}

View File

@@ -15,9 +15,6 @@ interface AlbumDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
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")
fun getAlbums(userId: Int): List<AlbumEntity>

View File

@@ -15,9 +15,6 @@ interface PhotoDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
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")
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")
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")
fun getAllUsers(): List<UserEntity>

View File

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

View File

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

View File

@@ -3,4 +3,6 @@ apply from: '../core.gradle'
dependencies {
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'

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:
active: true
EmptyFunctionBlock:
active: true
active: false
ignoreOverridden: false
EmptyIfBlock:
active: true

View File

@@ -3,4 +3,6 @@ apply from: '../core.gradle'
dependencies {
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()
}
}