From 75d7da58555d4694fc3cea1bed4d176cabaf5f29 Mon Sep 17 00:00:00 2001 From: Praveen Date: Wed, 29 Dec 2021 04:48:52 +0530 Subject: [PATCH 1/2] Roboelectric dependency version updated to 4.7.3 --- .../blueprints/todoapp/tasks/TasksViewModelTest.kt | 3 ++- build.gradle | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/test/java/com/example/android/architecture/blueprints/todoapp/tasks/TasksViewModelTest.kt b/app/src/test/java/com/example/android/architecture/blueprints/todoapp/tasks/TasksViewModelTest.kt index bc9e1ce19..2fc8ad927 100644 --- a/app/src/test/java/com/example/android/architecture/blueprints/todoapp/tasks/TasksViewModelTest.kt +++ b/app/src/test/java/com/example/android/architecture/blueprints/todoapp/tasks/TasksViewModelTest.kt @@ -16,6 +16,7 @@ package com.example.android.architecture.blueprints.todoapp.tasks +import android.os.Build import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -28,7 +29,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config -@Config(sdk = [30]) // https://github.com/robolectric/robolectric/pull/6776 +@Config(sdk = [Build.VERSION_CODES.S]) // https://github.com/robolectric/robolectric/pull/6776 @RunWith(AndroidJUnit4::class) class TasksViewModelTest { diff --git a/build.gradle b/build.gradle index 5ee3fda1c..ae5b69b32 100644 --- a/build.gradle +++ b/build.gradle @@ -48,7 +48,7 @@ ext { junitVersion = '4.13.2' materialVersion = '1.4.0' recyclerViewVersion = '1.2.1' - robolectricVersion = '4.5.1' + robolectricVersion = '4.7.3' roomVersion = '2.3.0' rulesVersion = '1.0.1' swipeRefreshLayoutVersion = '1.1.0' From 474b212a1c8b7baa826cbddb1e8939f4a043c224 Mon Sep 17 00:00:00 2001 From: Praveen Date: Wed, 29 Dec 2021 06:15:01 +0530 Subject: [PATCH 2/2] Successfully test repository indivisually using Fake Data source and Manual constructor dependency injection --- app/build.gradle | 1 + .../data/source/DefaultTasksRepository.kt | 23 +++---- .../todoapp/data/source/TasksDataSource.kt | 1 - .../data/source/DefaultTasksRepositoryTest.kt | 52 +++++++++++++++ .../todoapp/data/source/FakeDataSource.kt | 66 +++++++++++++++++++ 5 files changed, 127 insertions(+), 16 deletions(-) create mode 100644 app/src/test/java/com/example/android/architecture/blueprints/todoapp/data/source/DefaultTasksRepositoryTest.kt create mode 100644 app/src/test/java/com/example/android/architecture/blueprints/todoapp/data/source/FakeDataSource.kt diff --git a/app/build.gradle b/app/build.gradle index e93a7cb74..25dc7cab5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -60,6 +60,7 @@ 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" // AndroidX Test - JVM testing testImplementation "androidx.test:core-ktx:$androidXTestCoreVersion" diff --git a/app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/source/DefaultTasksRepository.kt b/app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/source/DefaultTasksRepository.kt index a4123e54c..1336908b6 100644 --- a/app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/source/DefaultTasksRepository.kt +++ b/app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/source/DefaultTasksRepository.kt @@ -33,11 +33,11 @@ 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 @@ -45,22 +45,15 @@ class DefaultTasksRepository private constructor(application: Application) { 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> { if (forceUpdate) { try { diff --git a/app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/source/TasksDataSource.kt b/app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/source/TasksDataSource.kt index bf3a11996..5342ada0f 100644 --- a/app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/source/TasksDataSource.kt +++ b/app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/source/TasksDataSource.kt @@ -24,7 +24,6 @@ import com.example.android.architecture.blueprints.todoapp.data.Task */ interface TasksDataSource { - fun observeTasks(): LiveData>> suspend fun getTasks(): Result> diff --git a/app/src/test/java/com/example/android/architecture/blueprints/todoapp/data/source/DefaultTasksRepositoryTest.kt b/app/src/test/java/com/example/android/architecture/blueprints/todoapp/data/source/DefaultTasksRepositoryTest.kt new file mode 100644 index 000000000..cf5429a46 --- /dev/null +++ b/app/src/test/java/com/example/android/architecture/blueprints/todoapp/data/source/DefaultTasksRepositoryTest.kt @@ -0,0 +1,52 @@ +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.junit.Assert.* +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()) + // Get a reference to the class under test + tasksRepository = DefaultTasksRepository( + // TODO Dispatchers.Unconfined should be replaced with Dispatchers.Main + // this requires understanding more about coroutines + testing + // so we will keep this as Unconfined for now. + tasksRemoteDataSource, tasksLocalDataSource, Dispatchers.Unconfined + ) + } + + @Test + fun getTasks_requestsAllTasksFromRemoteDataSource() = runBlockingTest{ + // When tasks are requested from the tasks repository + val tasks = tasksRepository.getTasks(true) as Result.Success + + // Then tasks are loaded from the remote data source + assertThat(tasks.data, IsEqual(remoteTasks)) + } +} \ No newline at end of file diff --git a/app/src/test/java/com/example/android/architecture/blueprints/todoapp/data/source/FakeDataSource.kt b/app/src/test/java/com/example/android/architecture/blueprints/todoapp/data/source/FakeDataSource.kt new file mode 100644 index 000000000..81a9a3b56 --- /dev/null +++ b/app/src/test/java/com/example/android/architecture/blueprints/todoapp/data/source/FakeDataSource.kt @@ -0,0 +1,66 @@ +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.Task + +class FakeDataSource(var tasks:MutableList? = mutableListOf()) : TasksDataSource { + override fun observeTasks(): LiveData>> { + TODO("Not yet implemented") + } + + override suspend fun getTasks(): Result> { + tasks?.let { + return Result.Success(ArrayList(it)) + } + return Result.Error(Exception("Task not found")) + } + + override suspend fun refreshTasks() { + TODO("Not yet implemented") + } + + override fun observeTask(taskId: String): LiveData> { + TODO("Not yet implemented") + } + + override suspend fun getTask(taskId: String): Result { + 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") + } +} \ No newline at end of file