Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AC-1092: Add Tests for Observation Repository #1010

Merged
merged 3 commits into from
Aug 27, 2023
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ import com.openmrs.android_sdk.library.databases.entities.ObservationEntity
import com.openmrs.android_sdk.library.models.Observation
import com.openmrs.android_sdk.library.models.Resource
import com.openmrs.android_sdk.utilities.NetworkUtils
import com.openmrs.android_sdk.utilities.execute
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import java.util.concurrent.Callable
import javax.inject.Inject
import javax.inject.Singleton
Expand Down Expand Up @@ -132,19 +131,8 @@ class ObservationRepository @Inject constructor(

val observationResources: List<Resource> = this.body()!!.results
for (observationResource in observationResources) {
getObservationByUuid(observationResource.uuid!!).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ observation ->
observationList.add(observation)
},
{ error ->
Log.e("Observation Repository", "Error: ${error.message}")
},
{
Log.d("Observation Repository", "Observable completed")
}
)
val observation = getObservationByUuid(observationResource.uuid!!)
observationList.add(observation.execute())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we removing this subscriber and observer???

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rishabh-997 I was the one who added this code but when unit tests were failing I noticed this was a bug. I replaced it with "execute()" method which returns the immediate result of observable available.
"fun Observable.execute(): T = this.single().toBlocking().first()"
Like this I did many refactoring in my earlier written code.
Regardless the api call is still kept as an observable so that implementers of SDK can implement it in that pattern if wanted.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we are blocking the thread till the execution is completed instead of handling in backgraound and wait for results?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RestApi function gives the result immediately. And the result won't emit any addition to the observale too, since there are no triggers to the restApi function except for once in this ObservationRepository function.

I guess the restApi functions were kept to return observales since It's a good practice to maintain consistency in the codebase, if the majority of codebase uses reactive programming, returning an Observable or a Flow from Retrofit API functions would align well with the overall architecture of the SDK.

So here we are just fetching the immediate result, instead of waiting for more results because there are absolutely zero changes to the result after the initial execution. So, It won't be any worth for the thread to keep waiting.

}
observationDAO.deleteAllStandaloneObservations(uuid) //delete previous list
observationDAO.saveStandaloneObservations(observationList) //save latest list
Expand Down Expand Up @@ -172,19 +160,8 @@ class ObservationRepository @Inject constructor(

val observationResources: List<Resource> = this.body()!!.results
for (observationResource in observationResources) {
getObservationByUuid(observationResource.uuid!!).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ observation ->
observationList.add(observation)
},
{ error ->
Log.e("Observation Repository", "Error: ${error.message}")
},
{
Log.d("Observation Repository", "Observable completed")
}
)
val observation = getObservationByUuid(observationResource.uuid!!)
observationList.add(observation.execute())
}
observationDAO.saveStandaloneObservations(observationList) //save latest list
return Observable.just(observationList.toList())
Expand Down Expand Up @@ -213,19 +190,8 @@ class ObservationRepository @Inject constructor(

val observationResources: List<Resource> = this.body()!!.results
for (observationResource in observationResources) {
getObservationByUuid(observationResource.uuid!!).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ observation ->
observationList.add(observation)
},
{ error ->
Log.e("Observation Repository", "Error: ${error.message}")
},
{
Log.d("Observation Repository", "Observable completed")
}
)
val observation = getObservationByUuid(observationResource.uuid!!)
observationList.add(observation.execute())
}
observationDAO.saveStandaloneObservations(observationList) //save latest list
return Observable.just(observationList.toList())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,6 @@ import java.io.ByteArrayOutputStream
import java.util.concurrent.Callable

object AppDatabaseHelper {
val encounterRoomDAO: EncounterRoomDAO = AppDatabase.getDatabase(
OpenmrsAndroid.getInstance()!!.applicationContext
).encounterRoomDAO()

@JvmStatic
fun convert(obs: Observation, encounterID: Long): ObservationEntity {
Expand All @@ -87,6 +84,9 @@ object AppDatabaseHelper {

@JvmStatic
fun convert(obs: ObservationEntity): Observation {
val encounterRoomDAO: EncounterRoomDAO = AppDatabase.getDatabase(
OpenmrsAndroid.getInstance()!!.applicationContext
).encounterRoomDAO()

val observation = Observation()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package com.openmrs.android_sdk.library.api.repository

import android.content.Context
import com.google.gson.GsonBuilder
import com.openmrs.android_sdk.library.OpenmrsAndroid
import com.openmrs.android_sdk.library.api.RestApi
import com.openmrs.android_sdk.library.dao.ObservationRoomDAO
import com.openmrs.android_sdk.library.databases.AppDatabase
import com.openmrs.android_sdk.library.databases.AppDatabaseHelper
import com.openmrs.android_sdk.library.databases.entities.ObservationEntity
import com.openmrs.android_sdk.library.models.Observation
import com.openmrs.android_sdk.utilities.NetworkUtils
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
import io.mockk.clearAllMocks
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okio.buffer
import okio.source
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Inject

@HiltAndroidTest
@RunWith(RobolectricTestRunner::class)
@Config(application = HiltTestApplication::class)
class ObservationRepositoryTest {
lateinit var mockWebServer: MockWebServer
lateinit var observationApi: RestApi

@get:Rule
var hiltRule = HiltAndroidRule(this)

val context: Context = mockk(relaxed = true)
val appContext: Context = mockk(relaxed = true)
val appDatabase: AppDatabase = mockk()
val observationRoomDAO: ObservationRoomDAO = mockk(relaxed = true)

@Before
fun init() {
every { context.getApplicationContext() } returns appContext
mockkStatic(OpenmrsAndroid::class)
every { OpenmrsAndroid.getInstance() } returns context
mockkStatic(AppDatabase::class)
every { AppDatabase.getDatabase(appContext) } returns appDatabase
every { appDatabase.observationRoomDAO() } returns observationRoomDAO

mockkStatic(NetworkUtils::class)
every { NetworkUtils.isOnline() } returns true

mockWebServer = MockWebServer()
mockWebServer.start()

val gsonBuilder = GsonBuilder()
val myGson = gsonBuilder
.excludeFieldsWithoutExposeAnnotation()
.create()

val retrofit = Retrofit.Builder()
.baseUrl(mockWebServer.url("/"))
.addConverterFactory(GsonConverterFactory.create(myGson))
.build()

observationApi = retrofit.create(RestApi::class.java)
hiltRule.inject()
}

@After
fun tearDown() {
clearAllMocks()
mockWebServer.shutdown()
}

@Inject
lateinit var observationRepository: ObservationRepository

@Test
fun `createObservationFromLocal success return Observation`(){
enqueueMockResponse("mocked_responses/ObservationRepository/ObservationPost-success.json")

observationRepository.restApi = observationApi

val observationEntity: ObservationEntity = mockk(relaxed = true)
val observationToPost = Observation()

mockkStatic(AppDatabaseHelper::class)
every { AppDatabaseHelper.convert(observationEntity) } returns observationToPost

val observation = observationRepository.createObservationFromLocal(observationEntity).toBlocking().first()

assertEquals(observation.uuid, "12345")
assertEquals(observation.display, "obsDisplay")
assertEquals(observation.value, "120/80")
assertEquals(observation.obsDatetime, "2023-07-27 12:34:56")
assertEquals(observation.accessionNumber, 987)
}

@Test
fun `getObservationByUuid success return Observation`(){
enqueueMockResponse("mocked_responses/ObservationRepository/ObservationPost-success.json")

observationRepository.restApi = observationApi
val testUuid = "12345"
val observation = observationRepository.getObservationByUuid(testUuid).toBlocking().first()

assertEquals(observation.uuid, "12345")
assertEquals(observation.display, "obsDisplay")
assertEquals(observation.value, "120/80")
assertEquals(observation.obsDatetime, "2023-07-27 12:34:56")
assertEquals(observation.accessionNumber, 987)
}

@Test
fun `getAllObservationResourcesByPatientUuid success return List of Resources`(){
enqueueMockResponse("mocked_responses/ObservationRepository/ObservationResources-Get.json")

observationRepository.restApi = observationApi
val patientUuid = "070f0120-0283-4858-885d-a20d967729cf"
val observation = observationRepository.getAllObservationResourcesByPatientUuid(patientUuid).toBlocking().first().get(1)

assertEquals(observation.uuid, "99a0c42b-d50e-4ae3-b826-d1959c737e74")
assertEquals(observation.display, "Visit Diagnoses: Primary, Confirmed diagnosis, Disease of bone and joint")
}

@Test
fun `getAllObservationResourcesByEncounterUuid success return List of Resources`(){
enqueueMockResponse("mocked_responses/ObservationRepository/ObservationResources-Get.json")

observationRepository.restApi = observationApi
val encounterUuid = "11c22e25-f9f8-4c79-b384-1da39ee7d5d2"
val observation = observationRepository.getAllObservationResourcesByEncounterUuid(encounterUuid).toBlocking().first().get(2)

assertEquals(observation.uuid, "33c83406-cd64-4c04-9506-7bdf754570a3")
assertEquals(observation.display, "Diagnosis order: Primary")
}

@Test
fun `getAllObservationsByPatientUuidAndSaveLocally success return List of Observations`(){
enqueueMockResponse("mocked_responses/ObservationRepository/ObservationResources-Get.json")
enqueueMockResponse("mocked_responses/ObservationRepository/ObservationPost-success.json")
enqueueMockResponse("mocked_responses/ObservationRepository/ObservationPost-success.json")
enqueueMockResponse("mocked_responses/ObservationRepository/ObservationPost-success.json")
enqueueMockResponse("mocked_responses/ObservationRepository/ObservationPost-success.json")

observationRepository.restApi = observationApi
val patientUuid = "070f0120-0283-4858-885d-a20d967729cf"
val observation = observationRepository.getAllObservationsByPatientUuidAndSaveLocally(patientUuid).toBlocking().first().get(0)

assertEquals(observation.uuid, "12345")
assertEquals(observation.display, "obsDisplay")
}

@Test
fun `getAllObservationsByEncounterUuidAndSaveLocally success return List of Observations`(){
enqueueMockResponse("mocked_responses/ObservationRepository/ObservationResources-Get.json")
enqueueMockResponse("mocked_responses/ObservationRepository/ObservationPost-success.json")
enqueueMockResponse("mocked_responses/ObservationRepository/ObservationPost-success.json")
enqueueMockResponse("mocked_responses/ObservationRepository/ObservationPost-success.json")
enqueueMockResponse("mocked_responses/ObservationRepository/ObservationPost-success.json")

observationRepository.restApi = observationApi
val encounterUuid = "11c22e25-f9f8-4c79-b384-1da39ee7d5d2"
val observation = observationRepository.getAllObservationsByPatientUuidAndSaveLocally(encounterUuid).toBlocking().first().get(0)

assertEquals(observation.uuid, "12345")
assertEquals(observation.display, "obsDisplay")
}

fun enqueueMockResponse(fileName: String) {
javaClass.classLoader?.let {
val inputStream = it.getResourceAsStream(fileName)
val source = inputStream.source().buffer()
val mockResponse = MockResponse()
mockResponse.setBody(source.readString(Charsets.UTF_8))
mockResponse.setResponseCode(200)
mockWebServer.enqueue(mockResponse)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"uuid": "12345",
"display": "obsDisplay",
"concept": {
"uuid": "abc123",
"display": "Blood Pressure"
},
"value": "120/80",
"person": {
"uuid": "xyz789",
"display": "John Doe"
},
"obsDatetime": "2023-07-27 12:34:56",
"accessionNumber": 987,
"obsGroup": {
"id": 56789,
"concept": {
"uuid": "def456",
"display": "Temperature"
},
"value": "98.6",
"person": {
"uuid": "uvw987",
"display": "Jane Smith"
},
"obsDatetime": "2023-07-27 13:45:30",
"obsGroup": null,
"valueCodedName": "Normal",
"comment": "No concerns",
"location": {
"uuid": "mno654",
"display": "Clinic A"
},
"encounter": {
"uuid": "pqr321",
"display": "Initial Checkup"
},
"voided": false,
"formFieldPath": "/encounter/observations[0]/value",
"formFieldNamespace": "http://example.org/form",
"groupMembers": null,
"order": "Labs",
"status": "Final"
},
"valueCodedName": "Normal",
"comment": "No issues",
"location": {
"uuid": "mno654",
"display": "Clinic A"
},
"encounter": {
"uuid": "pqr321",
"display": "Initial Checkup"
},
"voided": false,
"formFieldPath": "/encounter/observations[0]/value",
"formFieldNamespace": "http://example.org/form",
"groupMembers": null,
"order": "Labs",
"status": "Final"
}
Loading