Skip to content

Commit

Permalink
add fake data source and using dependency injection
Browse files Browse the repository at this point in the history
  • Loading branch information
solygambas committed Apr 21, 2022
1 parent 06eb48e commit 4a39fc9
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 18 deletions.
8 changes: 8 additions & 0 deletions 25-to-do-notes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ A basic app to learn how to test an Android project.
- setting up, running, and interpreting both local and instrumental tests in Android.
- writing unit tests in Android using JUnit4 and Hamcrest.
- writing simple LiveData and ViewModel tests with the AndroidX Test Library.
- making a fake data source.
- writing a test using dependency injection.
- setting up a fake repository.
- using the fake repository inside a ViewModel.
- launching a fragment from a test.
- making a ServiceLocator.
- writing an integration test with Espresso UI testing framework.
- using Mockito to write Navigation tests.

Based on 3 tutorials by Google Codelabs (2022):

Expand Down
25 changes: 24 additions & 1 deletion 25-to-do-notes/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ dependencies {
implementation "androidx.annotation:annotation:$androidXAnnotations"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
implementation "com.jakewharton.timber:timber:$timberVersion"
implementation "androidx.test.espresso:espresso-idling-resource:$espressoVersion"
implementation "androidx.room:room-runtime:$roomVersion"
kapt "androidx.room:room-compiler:$roomVersion"

// Architecture Components
implementation "androidx.room:room-runtime:$roomVersion"
Expand All @@ -60,6 +63,24 @@ dependencies {
testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
testImplementation "org.robolectric:robolectric:$robolectricVersion"
testImplementation ("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion") {
// https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-debug#debug-agent-and-android
exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug"
}

// Dependencies for Android instrumented unit tests
androidTestImplementation "junit:junit:$junitVersion"
androidTestImplementation "org.mockito:mockito-core:$mockitoVersion"
androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito:$dexMakerVersion"
androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion") {
// https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-debug#debug-agent-and-android
exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug"
}

// Testing code should not be included in the main code.
// Once https://issuetracker.google.com/128612536 is fixed this can be fixed.
debugImplementation "androidx.fragment:fragment-testing:$fragmentVersion"
implementation "androidx.test:core:$androidXTestCoreVersion"

// AndroidX Test - JVM testing
testImplementation "androidx.test:core-ktx:$androidXTestCoreVersion"
Expand All @@ -68,8 +89,10 @@ dependencies {
// AndroidX Test - Instrumented testing
androidTestImplementation "androidx.test.ext:junit:$androidXTestExtKotlinRunnerVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
androidTestImplementation "androidx.arch.core:core-testing:$archTestingVersion"

// Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
implementation "androidx.fragment:fragment-ktx:$fragmentKtxVersion"
implementation "androidx.fragment:fragment-ktx:$fragmentVersion"
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,34 +33,27 @@ import kotlinx.coroutines.withContext
/**
* Concrete implementation to load tasks from the data sources into a cache.
*/
class DefaultTasksRepository private constructor(application: Application) {

private val tasksRemoteDataSource: TasksDataSource
private val tasksLocalDataSource: TasksDataSource
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) {

companion object {
@Volatile
private var INSTANCE: DefaultTasksRepository? = null

fun getRepository(app: Application): DefaultTasksRepository {
return INSTANCE ?: synchronized(this) {
DefaultTasksRepository(app).also {
val database = Room.databaseBuilder(app,
ToDoDatabase::class.java, "Tasks.db").build()
DefaultTasksRepository(TasksRemoteDataSource, TasksLocalDataSource(database.taskDao())).also {
INSTANCE = it
}
}
}
}

init {
val database = Room.databaseBuilder(application.applicationContext,
ToDoDatabase::class.java, "Tasks.db")
.build()

tasksRemoteDataSource = TasksRemoteDataSource
tasksLocalDataSource = TasksLocalDataSource(database.taskDao())
}

suspend fun getTasks(forceUpdate: Boolean = false): Result<List<Task>> {
if (forceUpdate) {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.example.android.architecture.blueprints.todoapp.data.source

import com.example.android.architecture.blueprints.todoapp.data.Result
import com.example.android.architecture.blueprints.todoapp.data.Task
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import org.hamcrest.core.IsEqual
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Before

import org.junit.Test

@ExperimentalCoroutinesApi
class DefaultTasksRepositoryTest {
private val task1 = Task("Title1", "Description1")
private val task2 = Task("Title2", "Description2")
private val task3 = Task("Title3", "Description3")
private val remoteTasks = listOf(task1, task2).sortedBy { it.id }
private val localTasks = listOf(task3).sortedBy { it.id }
private val newTasks = listOf(task3).sortedBy { it.id }

private lateinit var tasksRemoteDataSource: FakeDataSource
private lateinit var tasksLocalDataSource: FakeDataSource
// Class under test
private lateinit var tasksRepository: DefaultTasksRepository

@Before
fun createRepository() {
tasksRemoteDataSource = FakeDataSource(remoteTasks.toMutableList())
tasksLocalDataSource = FakeDataSource(localTasks.toMutableList())
tasksRepository = DefaultTasksRepository(
tasksRemoteDataSource, tasksLocalDataSource, Dispatchers.Unconfined
)
}

@Test
fun getTasks_requestsAllTasksFromRemoteDataSource() = runBlockingTest {
val tasks = tasksRepository.getTasks(true) as Result.Success
assertThat(tasks.data, IsEqual(remoteTasks))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.example.android.architecture.blueprints.todoapp.data.source

import androidx.lifecycle.LiveData
import com.example.android.architecture.blueprints.todoapp.data.Result
import com.example.android.architecture.blueprints.todoapp.data.Result.Error
import com.example.android.architecture.blueprints.todoapp.data.Result.Success
import com.example.android.architecture.blueprints.todoapp.data.Task

class FakeDataSource(var tasks: MutableList<Task> = mutableListOf()) : TasksDataSource {
override fun observeTasks(): LiveData<Result<List<Task>>> {
TODO("Not yet implemented")
}

override suspend fun getTasks(): Result<List<Task>> {
tasks?.let { return Success(ArrayList(it)) }
return Error(
Exception("Tasks not found")
)
}

override suspend fun refreshTasks() {
TODO("Not yet implemented")
}

override fun observeTask(taskId: String): LiveData<Result<Task>> {
TODO("Not yet implemented")
}

override suspend fun getTask(taskId: String): Result<Task> {
TODO("Not yet implemented")
}

override suspend fun refreshTask(taskId: String) {
TODO("Not yet implemented")
}

override suspend fun saveTask(task: Task) {
tasks?.add(task)
}

override suspend fun completeTask(task: Task) {
TODO("Not yet implemented")
}

override suspend fun completeTask(taskId: String) {
TODO("Not yet implemented")
}

override suspend fun activateTask(task: Task) {
TODO("Not yet implemented")
}

override suspend fun activateTask(taskId: String) {
TODO("Not yet implemented")
}

override suspend fun clearCompletedTasks() {
TODO("Not yet implemented")
}

override suspend fun deleteAllTasks() {
tasks?.clear()
}

override suspend fun deleteTask(taskId: String) {
TODO("Not yet implemented")
}
}
8 changes: 5 additions & 3 deletions 25-to-do-notes/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ ext {
targetSdkVersion = 31
compileSdkVersion = 31

// App dependencies
// App dependencies
androidXVersion = '1.0.0'
androidXTestCoreVersion = '1.4.0'
androidXTestExtKotlinRunnerVersion = '1.1.3'
Expand All @@ -41,12 +41,14 @@ ext {
coroutinesVersion = '1.5.2'
cardVersion = '1.0.0'
coroutinesVersion = '1.5.0'
dexMakerVersion = '2.12.1'
dexMakerVersion = '2.28.1'
espressoVersion = '3.4.0'
fragmentKtxVersion = '1.4.0'
fragmentVersion = '1.4.0'
hamcrestVersion = '1.3'
junitVersion = '4.13.2'
materialVersion = '1.4.0'
mockitoVersion = '3.4.6'
multiDexVersion = '2.0.1'
recyclerViewVersion = '1.2.1'
robolectricVersion = '4.5.1'
roomVersion = '2.3.0'
Expand Down

0 comments on commit 4a39fc9

Please sign in to comment.