From 416a5b0506ddd626fee09182e281ccf8feaac356 Mon Sep 17 00:00:00 2001 From: paul sandjong Date: Thu, 11 Dec 2025 09:49:51 +0100 Subject: [PATCH 1/3] test claimAct.saveClaim --- claimManagement/build.gradle | 19 ++- .../openimis/imisclaims/ClaimActivity.java | 2 +- .../imisclaims/ClaimActivityTest.java | 139 ++++++++++++++++++ .../ClaimManagementActivityTest.java | 7 - 4 files changed, 158 insertions(+), 9 deletions(-) create mode 100644 claimManagement/src/test/java/org/openimis/imisclaims/ClaimActivityTest.java delete mode 100644 claimManagement/src/test/java/org/openimis/imisclaims/ClaimManagementActivityTest.java diff --git a/claimManagement/build.gradle b/claimManagement/build.gradle index c3a245d7..8cd81622 100644 --- a/claimManagement/build.gradle +++ b/claimManagement/build.gradle @@ -180,6 +180,12 @@ android { packagingOptions { exclude 'META-INF/DEPENDENCIES' } + + testOptions { + unitTests { + includeAndroidResources = true + } + } } apollo { @@ -193,6 +199,13 @@ apollo { ] } +tasks.withType(Test).configureEach { + testLogging { + events "passed", "skipped", "failed" + exceptionFormat "full" + showStandardStreams = false + } +} // Apply custom flavours if(file('custom-flavours.gradle').exists()){ @@ -215,9 +228,13 @@ dependencies { implementation ('com.apollographql.apollo:apollo-android-support:2.5.14'){ because("Apollo 3+ only works with Kotlin coroutines") } - testImplementation 'junit:junit:4.12' + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.mockito:mockito-core:5.5.0' + testImplementation 'org.robolectric:robolectric:4.11.1' + testImplementation 'androidx.test:core:1.5.0' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation 'androidx.test:core:1.5.0' implementation group: 'com.squareup.picasso', name: 'picasso', version: '2.71828' implementation group: 'net.lingala.zip4j', name: 'zip4j', version: '1.2.7' diff --git a/claimManagement/src/main/java/org/openimis/imisclaims/ClaimActivity.java b/claimManagement/src/main/java/org/openimis/imisclaims/ClaimActivity.java index e9894971..70990e8e 100644 --- a/claimManagement/src/main/java/org/openimis/imisclaims/ClaimActivity.java +++ b/claimManagement/src/main/java/org/openimis/imisclaims/ClaimActivity.java @@ -811,7 +811,7 @@ protected void confirmNewDialog(String msg) { runOnUiThread(() -> showDialog(msg, (dialog, which) -> ClearForm(), (dialog, which) -> dialog.dismiss())); } - private boolean saveClaim() { + protected boolean saveClaim() { Intent intent = getIntent(); String claimUUID; if (intent.hasExtra(EXTRA_CLAIM_UUID)) { diff --git a/claimManagement/src/test/java/org/openimis/imisclaims/ClaimActivityTest.java b/claimManagement/src/test/java/org/openimis/imisclaims/ClaimActivityTest.java new file mode 100644 index 00000000..1255f9ba --- /dev/null +++ b/claimManagement/src/test/java/org/openimis/imisclaims/ClaimActivityTest.java @@ -0,0 +1,139 @@ +package org.openimis.imisclaims; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import android.app.Activity; +import android.content.ContentValues; +import android.content.res.Resources; +import android.widget.AutoCompleteTextView; +import android.widget.CheckBox; +import android.widget.EditText; +import android.text.SpannableStringBuilder; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.openimis.imisclaims.tools.StorageManager; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; + +import java.util.ArrayList; +import java.util.HashMap; + +@RunWith(RobolectricTestRunner.class) +public class ClaimActivityTest { + @Mock + Global global; + + @Mock + SQLHandler sqlHandler; + + @Mock + Activity activity; + + @Mock + Resources resources; + + @Mock + StorageManager storageManager; + + ClaimActivity claimActivity; + + @Before + public void setup() { + MockitoAnnotations.openMocks(this); + when(activity.getResources()).thenReturn(resources); + when(resources.getString(anyInt())).thenReturn("mockString"); + claimActivity = Robolectric.buildActivity(ClaimActivity.class).create().start().resume().visible().get(); + claimActivity.sqlHandler = sqlHandler; + claimActivity.global = global; + when(global.isNetworkAvailable()).thenReturn(true); // or false, it doesn't matter + } + + @Test + public void testSaveClaim_ShouldInsertInDatabase() { + EditText etHF = mock(EditText.class); + EditText etClaimAdmin = mock(EditText.class); + EditText etClaimCode = mock(EditText.class); + EditText etGuaranteeNo = mock(EditText.class); + EditText etCHFID = mock(EditText.class); + EditText etStart = mock(EditText.class); + EditText etEnd = mock(EditText.class); + + AutoCompleteTextView etDiagnosis = mock(AutoCompleteTextView.class); + AutoCompleteTextView etDiagnosis1 = mock(AutoCompleteTextView.class); + AutoCompleteTextView etDiagnosis2 = mock(AutoCompleteTextView.class); + AutoCompleteTextView etDiagnosis3 = mock(AutoCompleteTextView.class); + AutoCompleteTextView etDiagnosis4 = mock(AutoCompleteTextView.class); + + when(etHF.getText()).thenReturn(new SpannableStringBuilder("HF001")); + when(etClaimAdmin.getText()).thenReturn(new SpannableStringBuilder("ADMIN001")); + when(etClaimCode.getText()).thenReturn(new SpannableStringBuilder("CLM001")); + when(etGuaranteeNo.getText()).thenReturn(new SpannableStringBuilder("GNT001")); + when(etCHFID.getText()).thenReturn(new SpannableStringBuilder("INS12345")); + when(etStart.getText()).thenReturn(new SpannableStringBuilder("2025-12-01")); + when(etEnd.getText()).thenReturn(new SpannableStringBuilder("2025-12-09")); + when(etDiagnosis.getText()).thenReturn(new SpannableStringBuilder("A01")); + when(etDiagnosis1.getText()).thenReturn(new SpannableStringBuilder("B01")); + when(etDiagnosis2.getText()).thenReturn(new SpannableStringBuilder("C01")); + when(etDiagnosis3.getText()).thenReturn(new SpannableStringBuilder("D01")); + when(etDiagnosis4.getText()).thenReturn(new SpannableStringBuilder("E01")); + + AutoCompleteTextView etVisitType = mock(AutoCompleteTextView.class); + AutoCompleteTextView patientCondition = mock(AutoCompleteTextView.class); + when(etVisitType.getTag()).thenReturn("O"); + when(patientCondition.getTag()).thenReturn("A"); + + CheckBox etPreAuth = mock(CheckBox.class); + when(etPreAuth.isChecked()).thenReturn(false); + + claimActivity.etHealthFacility = etHF; + claimActivity.etClaimAdmin = etClaimAdmin; + claimActivity.etClaimCode = etClaimCode; + claimActivity.etGuaranteeNo = etGuaranteeNo; + claimActivity.etInsureeNumber = etCHFID; + claimActivity.etStartDate = etStart; + claimActivity.etEndDate = etEnd; + + claimActivity.etDiagnosis = etDiagnosis; + claimActivity.etDiagnosis1 = etDiagnosis1; + claimActivity.etDiagnosis2 = etDiagnosis2; + claimActivity.etDiagnosis3 = etDiagnosis3; + claimActivity.etDiagnosis4 = etDiagnosis4; + + claimActivity.etVisitType = etVisitType; + claimActivity.etPatientCondition = patientCondition; + claimActivity.etPreAuthorization = etPreAuth; + + ArrayList> fakeItems = new ArrayList<>(); + HashMap item1 = new HashMap<>(); + item1.put("Code", "ITEM001"); + item1.put("Price", "50"); + item1.put("Quantity", "1"); + fakeItems.add(item1); + claimActivity.lvItemList = fakeItems; + + ArrayList> fakeServices = new ArrayList<>(); + HashMap svc1 = new HashMap<>(); + svc1.put("Code", "SVC001"); + svc1.put("Price", "40"); + svc1.put("Quantity", "1"); + svc1.put("PackageType", "P"); + svc1.put("SubServicesItems", "[]"); + fakeServices.add(svc1); + claimActivity.lvServiceList = fakeServices; + + doNothing().when(sqlHandler).saveClaim(any(ContentValues.class), anyList(), anyList()); + + boolean result = claimActivity.saveClaim(); + + assertTrue(result); + verify(sqlHandler, times(1)) + .saveClaim(any(ContentValues.class), anyList(), anyList()); + } + +} diff --git a/claimManagement/src/test/java/org/openimis/imisclaims/ClaimManagementActivityTest.java b/claimManagement/src/test/java/org/openimis/imisclaims/ClaimManagementActivityTest.java deleted file mode 100644 index 31b92010..00000000 --- a/claimManagement/src/test/java/org/openimis/imisclaims/ClaimManagementActivityTest.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.openimis.imisclaims; - -import junit.framework.TestCase; - -public class ClaimManagementActivityTest extends TestCase { - -} \ No newline at end of file From 16594fbc859bfda547427c61f90fb6432967b269 Mon Sep 17 00:00:00 2001 From: paul sandjong Date: Thu, 11 Dec 2025 10:30:07 +0100 Subject: [PATCH 2/3] add pull_request on workflow --- .github/workflows/main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c20ef1e7..c8b7d201 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,6 +6,10 @@ on: - '*' # tags: # - '!v*' + pull_request: + branches: + - '*' + types: [opened, synchronize, reopened] jobs: build: From 55d107b91cd2ec1fa21217b6b0f5459b950a103d Mon Sep 17 00:00:00 2001 From: paul sandjong Date: Thu, 8 Jan 2026 13:59:36 +0100 Subject: [PATCH 3/3] Unit tests for SynchronizeService --- .gitignore | 3 + .../imisclaims/SynchronizeService.java | 12 +- .../imisclaims/SynchronizeServiceTest.java | 201 ++++++++++++++++++ 3 files changed, 210 insertions(+), 6 deletions(-) create mode 100644 claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java diff --git a/.gitignore b/.gitignore index 9fb66257..47792f4d 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,9 @@ proguard/ # Log Files *.log +# Java heap dump files +*.hprof + # Android Studio Navigation editor temp files .navigation/ diff --git a/claimManagement/src/main/java/org/openimis/imisclaims/SynchronizeService.java b/claimManagement/src/main/java/org/openimis/imisclaims/SynchronizeService.java index f7593030..0619ff37 100644 --- a/claimManagement/src/main/java/org/openimis/imisclaims/SynchronizeService.java +++ b/claimManagement/src/main/java/org/openimis/imisclaims/SynchronizeService.java @@ -54,9 +54,9 @@ public class SynchronizeService extends JobIntentService { private static final String claimResponseLine = "[%s] %s"; - private Global global; - private SQLHandler sqlHandler; - private StorageManager storageManager; + protected Global global; + protected SQLHandler sqlHandler; + protected StorageManager storageManager; @Override public void onCreate() { @@ -96,7 +96,7 @@ protected void onHandleWork(@NonNull Intent intent) { } } - private void handleUploadClaims() { + protected void handleUploadClaims() { if (!global.isNetworkAvailable()) { broadcastError(getResources().getString(R.string.CheckInternet), ACTION_UPLOAD_CLAIMS); return; @@ -118,7 +118,7 @@ private void handleUploadClaims() { } } - private JSONArray processClaimResponse(List results) { + protected JSONArray processClaimResponse(List results) { JSONArray jsonResults = new JSONArray(); String date = AppInformation.DateTimeInfo.getDefaultIsoDatetimeFormatter().format(new Date()); for (PostNewClaims.Result result : results) { @@ -252,7 +252,7 @@ private Uri createClaimExportZip(ArrayList exportedClaims) { zipFile); } - private void handleGetClaimCount() { + protected void handleGetClaimCount() { JSONObject counts = sqlHandler.getClaimCounts(); int enteredCount = counts.optInt(SQLHandler.CLAIM_UPLOAD_STATUS_ENTERED, 0); diff --git a/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java b/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java new file mode 100644 index 00000000..37c7eee3 --- /dev/null +++ b/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java @@ -0,0 +1,201 @@ +package org.openimis.imisclaims; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.openimis.imisclaims.usecase.PostNewClaims; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import org.openimis.imisclaims.tools.StorageManager; + +import java.util.Arrays; + +@RunWith(RobolectricTestRunner.class) +@Config(sdk = 28, manifest = Config.NONE) +public class SynchronizeServiceTest { + + @Mock private Global global; + @Mock private SQLHandler sqlHandler; + @Mock private Resources resources; + @Mock private Context context; + @Mock private StorageManager storageManager; + + private SynchronizeService synchronizeService; + + @Before + public void setUp() { + MockitoAnnotations.openMocks(this); + + synchronizeService = new SynchronizeService() { + @Override + public Context getApplicationContext() { + return context; + } + + @Override + public Resources getResources() { + return resources; + } + + @Override + public void sendBroadcast(Intent intent) { + // override to avoid real broadcast and capture intents if needed + capturedIntent = intent; + } + }; + + synchronizeService.global = global; + synchronizeService.sqlHandler = sqlHandler; + synchronizeService.storageManager = storageManager; + + when(context.getResources()).thenReturn(resources); + when(resources.getString(anyInt())).thenReturn("Error message"); + } + + private Intent capturedIntent; + + // ---------------------------------- + // Tests handleUploadClaims + // ---------------------------------- + + @Test + public void handleUploadClaims_WhenNoNetwork_BroadcastsError() { + when(global.isNetworkAvailable()).thenReturn(false); + + synchronizeService.handleUploadClaims(); + + verify(global).isNetworkAvailable(); + assertNotNull(capturedIntent); + assertEquals(SynchronizeService.ACTION_SYNC_ERROR, capturedIntent.getAction()); + assertEquals("Error message", capturedIntent.getStringExtra(SynchronizeService.EXTRA_ERROR_MESSAGE)); + } + + @Test + public void handleUploadClaims_WhenNoPendingClaims_BroadcastsNoClaimError() throws JSONException { + when(global.isNetworkAvailable()).thenReturn(true); + when(sqlHandler.getAllPendingClaims()).thenReturn(new JSONArray()); + + synchronizeService.handleUploadClaims(); + + verify(sqlHandler).getAllPendingClaims(); + assertNotNull(capturedIntent); + assertEquals(SynchronizeService.ACTION_SYNC_ERROR, capturedIntent.getAction()); + assertEquals("Error message", capturedIntent.getStringExtra(SynchronizeService.EXTRA_ERROR_MESSAGE)); + } + + // Note : we can't test PostNewClaims here without refactoring + // We just test that the method doesn't crash with pending claims + @Test + public void handleUploadClaims_WithPendingClaims_DoesNotCrash() throws JSONException { + when(global.isNetworkAvailable()).thenReturn(true); + + JSONArray pendingClaims = new JSONArray(); + JSONObject claim = new JSONObject(); + claim.put("id", "claim123"); + pendingClaims.put(claim); + + when(sqlHandler.getAllPendingClaims()).thenReturn(pendingClaims); + + synchronizeService.handleUploadClaims(); + + verify(sqlHandler).getAllPendingClaims(); + } + + // ---------------------------------- + // Tests processClaimResponse + // ---------------------------------- + + @Test + public void processClaimResponse_WithRejectedClaim_UpdatesStatusToRejected() throws JSONException { + PostNewClaims.Result rejectedResult = + new PostNewClaims.Result("claim123", PostNewClaims.Result.Status.REJECTED, "Claim rejected"); + + when(sqlHandler.getClaimUUIDForCode("claim123")).thenReturn("uuid123"); + + JSONArray result = synchronizeService.processClaimResponse(Arrays.asList(rejectedResult)); + + verify(sqlHandler).insertClaimUploadStatus( + eq("uuid123"), + anyString(), + eq(SQLHandler.CLAIM_UPLOAD_STATUS_REJECTED), + isNull() + ); + + assertEquals(1, result.length()); + assertTrue(result.getString(0).contains("Claim rejected")); + } + + @Test + public void processClaimResponse_WithError_UpdatesStatusToError() throws JSONException { + PostNewClaims.Result errorResult = + new PostNewClaims.Result("claim123", PostNewClaims.Result.Status.ERROR, "Server error"); + + when(sqlHandler.getClaimUUIDForCode("claim123")).thenReturn("uuid123"); + + JSONArray result = synchronizeService.processClaimResponse(Arrays.asList(errorResult)); + + verify(sqlHandler).insertClaimUploadStatus( + eq("uuid123"), + anyString(), + eq(SQLHandler.CLAIM_UPLOAD_STATUS_ERROR), + eq("Server error") + ); + + assertEquals(1, result.length()); + } + + @Test + public void processClaimResponse_WithSuccess_UpdatesStatusToAccepted() throws JSONException { + PostNewClaims.Result successResult = + new PostNewClaims.Result("claim123", PostNewClaims.Result.Status.SUCCESS, null); + + when(sqlHandler.getClaimUUIDForCode("claim123")).thenReturn("uuid123"); + + JSONArray result = synchronizeService.processClaimResponse(Arrays.asList(successResult)); + + verify(sqlHandler).insertClaimUploadStatus( + eq("uuid123"), + anyString(), + eq(SQLHandler.CLAIM_UPLOAD_STATUS_ACCEPTED), + isNull() + ); + + assertEquals(0, result.length()); // no error message for SUCCESS + } + + // ---------------------------------- + // Test handleGetClaimCount + // ---------------------------------- + + @Test + public void handleGetClaimCount_BroadcastsCorrectCounts() throws JSONException { + JSONObject counts = new JSONObject(); + counts.put(SQLHandler.CLAIM_UPLOAD_STATUS_ENTERED, 5); + counts.put(SQLHandler.CLAIM_UPLOAD_STATUS_ACCEPTED, 3); + counts.put(SQLHandler.CLAIM_UPLOAD_STATUS_REJECTED, 2); + + when(sqlHandler.getClaimCounts()).thenReturn(counts); + + synchronizeService.handleGetClaimCount(); + + assertNotNull(capturedIntent); + assertEquals(SynchronizeService.ACTION_CLAIM_COUNT_RESULT, capturedIntent.getAction()); + assertEquals(5, capturedIntent.getIntExtra(SynchronizeService.EXTRA_CLAIM_COUNT_ENTERED, 0)); + assertEquals(3, capturedIntent.getIntExtra(SynchronizeService.EXTRA_CLAIM_COUNT_ACCEPTED, 0)); + assertEquals(2, capturedIntent.getIntExtra(SynchronizeService.EXTRA_CLAIM_COUNT_REJECTED, 0)); + } +}