Skip to content
This repository has been archived by the owner on Dec 11, 2024. It is now read-only.

Mc25 #290

Open
wants to merge 10 commits into
base: starter_code
Choose a base branch
from
Open

Mc25 #290

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ build
.project
.settings/
.classpath
java_pid55928.hprof
70 changes: 70 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ android {
enabled = true
enabledForTests = true
}

testOptions {
unitTests {
includeAndroidResources = true
}
}

}

dependencies {
Expand All @@ -40,6 +47,13 @@ dependencies {

// Architecture Components
implementation "androidx.room:room-runtime:$roomVersion"
implementation 'androidx.test.ext:junit-ktx:1.1.5'
implementation 'androidx.test.espresso:espresso-contrib:3.5.1'
testImplementation 'junit:junit:4.13.2'
testImplementation 'junit:junit:4.13.2'
testImplementation 'junit:junit:4.13.2'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'junit:junit:4.13.2'
kapt "androidx.room:room-compiler:$roomVersion"
implementation "androidx.room:room-ktx:$roomVersion"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$archLifecycleVersion"
Expand All @@ -60,4 +74,60 @@ dependencies {
// Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
implementation "androidx.fragment:fragment-ktx:$fragmentKtxVersion"

// Other dependencies
testImplementation "org.hamcrest:hamcrest:$hamcrestVersion"


// AndroidX Test - JVM testing
testImplementation "androidx.test:core-ktx:$androidXTestCoreVersion"


// Core library
androidTestImplementation "androidx.test:core:1.5.0"

// To use the androidx.test.core APIs
androidTestImplementation "androidx.test:core:1.5.0"
// Kotlin extensions for androidx.test.core
androidTestImplementation "androidx.test:core-ktx:1.5.0"

// To use the JUnit Extension APIs
androidTestImplementation "androidx.test.ext:junit:1.1.5"
// Kotlin extensions for androidx.test.ext.junit
androidTestImplementation "androidx.test.ext:junit-ktx:1.1.5"

// To use the Truth Extension APIs
androidTestImplementation "androidx.test.ext:truth:1.5.0"

// To use the androidx.test.runner APIs
androidTestImplementation "androidx.test:runner:1.5.2"

// To use android test orchestrator
androidTestUtil "androidx.test:orchestrator:1.4.2"

testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.9.2'

//To use InstantTaskExecutorRule
testImplementation "androidx.arch.core:core-testing:$archTestingVersion"

// test with coroutines
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"

// Dependencies for Android instrumented unit tests
androidTestImplementation "junit:junit:$junitVersion"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"

// 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"

// Dependencies for Android instrumented unit tests
androidTestImplementation "org.mockito:mockito-core:$mockitoVersion"

androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito:$dexMakerVersion"


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.map
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
import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepository
import kotlinx.coroutines.runBlocking


class FakeAndroidTestRepository : TasksRepository {

var tasksServiceData: LinkedHashMap<String, Task> = LinkedHashMap()

private var shouldReturnError = false

private val observableTasks = MutableLiveData<Result<List<Task>>>()

fun setReturnError(value: Boolean) {
shouldReturnError = value
}

override suspend fun refreshTasks() {
observableTasks.value = getTasks()
}

override suspend fun refreshTask(taskId: String) {
refreshTasks()
}

override fun observeTasks(): LiveData<Result<List<Task>>> {
runBlocking { refreshTasks() }
return observableTasks
}

override fun observeTask(taskId: String): LiveData<Result<Task>> {
runBlocking { refreshTasks() }
return observableTasks.map { tasks ->
when (tasks) {
is Result.Loading -> Result.Loading
is Error -> Error(tasks.exception)
is Success -> {
val task = tasks.data.firstOrNull() { it.id == taskId }
?: return@map Error(Exception("Not found"))
Success(task)
}
}
}
}

override suspend fun getTask(taskId: String, forceUpdate: Boolean): Result<Task> {
if (shouldReturnError) {
return Error(Exception("Test exception"))
}
tasksServiceData[taskId]?.let {
return Success(it)
}
return Error(Exception("Could not find task"))
}

override suspend fun getTasks(forceUpdate: Boolean): Result<List<Task>> {
if (shouldReturnError) {
return Error(Exception("Test exception"))
}
return Success(tasksServiceData.values.toList())
}

override suspend fun saveTask(task: Task) {
tasksServiceData[task.id] = task
}

override suspend fun completeTask(task: Task) {
val completedTask = Task(task.title, task.description, true, task.id)
tasksServiceData[task.id] = completedTask
}

override suspend fun completeTask(taskId: String) {
// Not required for the remote data source.
throw NotImplementedError()
}

override suspend fun activateTask(task: Task) {
val activeTask = Task(task.title, task.description, false, task.id)
tasksServiceData[task.id] = activeTask
}

override suspend fun activateTask(taskId: String) {
throw NotImplementedError()
}

override suspend fun clearCompletedTasks() {
tasksServiceData = tasksServiceData.filterValues {
!it.isCompleted
} as LinkedHashMap<String, Task>
}

override suspend fun deleteTask(taskId: String) {
tasksServiceData.remove(taskId)
refreshTasks()
}

override suspend fun deleteAllTasks() {
tasksServiceData.clear()
refreshTasks()
}


fun addTasks(vararg tasks: Task) {
for (task in tasks) {
tasksServiceData[task.id] = task
}
runBlocking { refreshTasks() }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.example.android.architecture.blueprints.todoapp.taskdetail

import FakeAndroidTestRepository
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.example.android.architecture.blueprints.todoapp.data.Task
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
import com.example.android.architecture.blueprints.todoapp.R
import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepository
import com.example.android.architecture.blueprints.todoapp.servicelocator.ServiceLocator
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import org.hamcrest.CoreMatchers.not
import org.junit.After
import org.junit.Before

@MediumTest
@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
class TaskDetailFragmentTest{

private lateinit var repository: TasksRepository

@Before
fun initRepository() {
repository = FakeAndroidTestRepository()
ServiceLocator.tasksRepository = repository
}

@After
fun cleanupDb() = runBlockingTest {
ServiceLocator.resetRepository()
}

@Test
fun activeTaskDetails_DisplayedInUi() = runBlockingTest{
// GIVEN - Add active (incomplete) task to the DB
val activeTask = Task("Active Task", "AndroidX Rocks", false)
repository.saveTask(activeTask)

// WHEN - Details fragment launched to display task
val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)

onView(withId(R.id.task_detail_title_text)).check(matches(isDisplayed()))
onView(withId(R.id.task_detail_title_text)).check(matches(withText("Active Task")))
onView(withId(R.id.task_detail_description_text)).check(matches(isDisplayed()))
onView(withId(R.id.task_detail_description_text)).check(matches(withText("AndroidX Rocks")))
// and make sure the "active" checkbox is shown unchecked
onView(withId(R.id.task_detail_complete_checkbox)).check(matches(isDisplayed()))
onView(withId(R.id.task_detail_complete_checkbox)).check(matches(not(isChecked())))

}

@Test
fun completedTaskDetails_DisplayedInUi() = runBlockingTest{
// GIVEN - Add active (incomplete) task to the DB
val activeTask = Task("Active Task 2", "AndroidX Rocks", true)
repository.saveTask(activeTask)

// WHEN - Details fragment launched to display task
val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)

onView(withId(R.id.task_detail_title_text)).check(matches(isDisplayed()))
onView(withId(R.id.task_detail_title_text)).check(matches(withText("Active Task 2")))
onView(withId(R.id.task_detail_description_text)).check(matches(isDisplayed()))
onView(withId(R.id.task_detail_description_text)).check(matches(withText("AndroidX Rocks")))
// and make sure the "active" checkbox is shown unchecked
onView(withId(R.id.task_detail_complete_checkbox)).check(matches(isDisplayed()))
onView(withId(R.id.task_detail_complete_checkbox)).check(matches(isChecked()))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.example.android.architecture.blueprints.todoapp.tasks

import FakeAndroidTestRepository
import android.os.Bundle
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.navigation.NavController
import androidx.navigation.Navigation
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.example.android.architecture.blueprints.todoapp.data.Task
import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepository
import com.example.android.architecture.blueprints.todoapp.servicelocator.ServiceLocator
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import org.junit.After
import org.junit.Assert.*
import org.junit.Before
import org.junit.runner.RunWith
import com.example.android.architecture.blueprints.todoapp.R
import org.junit.Test
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify

@RunWith(AndroidJUnit4::class)
@MediumTest
@ExperimentalCoroutinesApi
class TasksFragmentTest{

private lateinit var repository: TasksRepository

@Before
fun initRepository() {
repository = FakeAndroidTestRepository()
ServiceLocator.tasksRepository = repository
}

@After
fun cleanupDb() = runBlockingTest {
ServiceLocator.resetRepository()
}

@Test
fun clickTask_navigateToDetailFragmentOne() = runBlockingTest {
repository.saveTask(Task("TITLE1", "DESCRIPTION1", false, "id1"))
repository.saveTask(Task("TITLE2", "DESCRIPTION2", true, "id2"))

// GIVEN - On the home screen
val scenario = launchFragmentInContainer<TasksFragment>(Bundle(), R.style.AppTheme)

val navController = mock(NavController::class.java)

scenario.onFragment {
Navigation.setViewNavController(it.view!!, navController)
}

// WHEN - Click on the first list item
onView(withId(R.id.tasks_list))
.perform(
RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
hasDescendant(withText("TITLE1")), click()))


// THEN - Verify that we navigate to the first detail screen
verify(navController).navigate(
TasksFragmentDirections.actionTasksFragmentToTaskDetailFragment( "id1"))

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package com.example.android.architecture.blueprints.todoapp

import android.app.Application
import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepository
import com.example.android.architecture.blueprints.todoapp.servicelocator.ServiceLocator
import timber.log.Timber
import timber.log.Timber.DebugTree

Expand All @@ -28,6 +30,9 @@ import timber.log.Timber.DebugTree
*/
class TodoApplication : Application() {

val taskRepository: TasksRepository
get() = ServiceLocator.provideTasksRepository(this)

override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) Timber.plant(DebugTree())
Expand Down
Loading