From d64f5fa0ed2c492cfeed4dab231c1b441e45b11b Mon Sep 17 00:00:00 2001 From: Kenneth Soh Date: Thu, 2 Apr 2026 12:57:04 +0800 Subject: [PATCH] test: Add various unit tests --- helperlib/src/test/java/android/util/Log.java | 31 ++++ .../concurrent/CoroutineAsyncTaskTest.kt | 50 ++++++- .../helperlib/exceptions/ApiExceptionTest.kt | 32 +++++ .../helperlib/helpers/LogHelperTest.kt | 54 +++++++ .../helperlib/helpers/ValidationHelperTest.kt | 34 +++++ .../helperlib/objects/ApiResponseTest.kt | 136 ++++++++++++++++++ .../helperlib/objects/HttpRequestTest.kt | 49 +++++++ 7 files changed, 385 insertions(+), 1 deletion(-) create mode 100644 helperlib/src/test/java/com/itachi1706/helperlib/exceptions/ApiExceptionTest.kt create mode 100644 helperlib/src/test/java/com/itachi1706/helperlib/helpers/LogHelperTest.kt create mode 100644 helperlib/src/test/java/com/itachi1706/helperlib/helpers/ValidationHelperTest.kt create mode 100644 helperlib/src/test/java/com/itachi1706/helperlib/objects/ApiResponseTest.kt create mode 100644 helperlib/src/test/java/com/itachi1706/helperlib/objects/HttpRequestTest.kt diff --git a/helperlib/src/test/java/android/util/Log.java b/helperlib/src/test/java/android/util/Log.java index 8b151be..980eb4e 100644 --- a/helperlib/src/test/java/android/util/Log.java +++ b/helperlib/src/test/java/android/util/Log.java @@ -1,11 +1,22 @@ package android.util; public class Log { + public static final int VERBOSE = 2; + public static final int DEBUG = 3; + public static final int INFO = 4; + public static final int WARN = 5; + public static final int ERROR = 6; + public static int d(String tag, String msg) { System.out.println("DEBUG: " + tag + ": " + msg); return 0; } + public static int d(String tag, String msg, Throwable tr) { + System.out.println("DEBUG: " + tag + ": " + msg + " - " + tr); + return 0; + } + public static int i(String tag, String msg) { System.out.println("INFO: " + tag + ": " + msg); return 0; @@ -16,11 +27,31 @@ public static int w(String tag, String msg) { return 0; } + public static int w(String tag, Throwable tr) { + System.out.println("WARN: " + tag + ": " + tr); + return 0; + } + + public static int w(String tag, String msg, Throwable tr) { + System.out.println("WARN: " + tag + ": " + msg + " - " + tr); + return 0; + } + public static int e(String tag, String msg) { System.out.println("ERROR: " + tag + ": " + msg); return 0; } + public static int e(String tag, String msg, Throwable tr) { + System.out.println("ERROR: " + tag + ": " + msg + " - " + tr); + return 0; + } + + public static int wtf(String tag, String msg) { + System.out.println("WTF: " + tag + ": " + msg); + return 0; + } + public static int println(int priority, String tag, String msg) { System.out.println(priority + ": " + tag + ": " + msg); return 0; diff --git a/helperlib/src/test/java/com/itachi1706/helperlib/concurrent/CoroutineAsyncTaskTest.kt b/helperlib/src/test/java/com/itachi1706/helperlib/concurrent/CoroutineAsyncTaskTest.kt index 4f943ce..b29242a 100644 --- a/helperlib/src/test/java/com/itachi1706/helperlib/concurrent/CoroutineAsyncTaskTest.kt +++ b/helperlib/src/test/java/com/itachi1706/helperlib/concurrent/CoroutineAsyncTaskTest.kt @@ -12,7 +12,6 @@ import org.mockito.junit.MockitoJUnitRunner import org.mockito.kotlin.spy import kotlin.test.AfterTest import kotlin.test.BeforeTest -import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -128,9 +127,51 @@ class CoroutineAsyncTaskTest { assertEquals(50, task.progress) } + @Test + fun cancelWithMayInterruptFalseCancelsCompletedTask() = runBlocking { + task.execute("param1") + task.preJob?.join() + task.bgJob?.await() + + task.cancel(false) + delay(100) + + assertEquals(true, task.isCancelled) + assertEquals(Status.FINISHED, task.status) + } + + @Test + fun publishProgressAfterCancellationDoesNotInvokeProgressCallback() = runBlocking { + task.execute("param1") + task.preJob?.join() + task.cancel(true) + + task.publishProgress(99) + delay(100) + + assertEquals(null, task.progress) + assertEquals(0, task.progressUpdateCount) + } + + @Test + fun cancelAfterBackgroundCompletionInvokesOnCancelledWithResult() = runBlocking { + task.execute("param1") + task.preJob?.join() + task.bgJob?.await() + + task.cancel(true) + delay(100) + + assertEquals(1, task.cancelledInvocationCount) + assertEquals("Result", task.cancelledResult) + } + private class TestCoroutineAsyncTask : CoroutineAsyncTask("TestTask") { var result: String? = null var progress: Int? = null + var progressUpdateCount: Int = 0 + var cancelledResult: String? = null + var cancelledInvocationCount: Int = 0 override fun doInBackground(vararg params: String?): String { return "Result" @@ -142,6 +183,13 @@ class CoroutineAsyncTaskTest { override fun onProgressUpdate(vararg params: Int?) { this.progress = params[0] + this.progressUpdateCount++ + } + + override fun onCancelled(result: String?) { + this.cancelledResult = result + this.cancelledInvocationCount++ } } + } \ No newline at end of file diff --git a/helperlib/src/test/java/com/itachi1706/helperlib/exceptions/ApiExceptionTest.kt b/helperlib/src/test/java/com/itachi1706/helperlib/exceptions/ApiExceptionTest.kt new file mode 100644 index 0000000..d6a495c --- /dev/null +++ b/helperlib/src/test/java/com/itachi1706/helperlib/exceptions/ApiExceptionTest.kt @@ -0,0 +1,32 @@ +package com.itachi1706.helperlib.exceptions + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ApiExceptionTest { + + @Test + fun apiExceptionPreservesMessage() { + val exception = ApiException("Callback is not set") + + assertEquals("Callback is not set", exception.message) + } + + @Test + fun apiExceptionExtendsExceptionType() { + val exception = ApiException("Any message") + val asException: Exception = exception + + assertEquals("Any message", asException.message) + } + + @Test + fun apiExceptionHasNullCauseByDefault() { + val exception = ApiException("Any message") + + assertNull(exception.cause) + } +} + + diff --git a/helperlib/src/test/java/com/itachi1706/helperlib/helpers/LogHelperTest.kt b/helperlib/src/test/java/com/itachi1706/helperlib/helpers/LogHelperTest.kt new file mode 100644 index 0000000..8decb92 --- /dev/null +++ b/helperlib/src/test/java/com/itachi1706/helperlib/helpers/LogHelperTest.kt @@ -0,0 +1,54 @@ +package com.itachi1706.helperlib.helpers + +import com.itachi1706.helperlib.interfaces.LogHandler +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import kotlin.test.Test +import kotlin.test.assertEquals + +class LogHelperTest { + + @Test + fun getLogLevelCharReturnsErrorForError() { + assertEquals('E', LogHelper.getLogLevelChar(6)) + } + + @Test + fun getLogLevelCharReturnsWarnForWarn() { + assertEquals('W', LogHelper.getLogLevelChar(5)) + } + + @Test + fun getLogLevelCharReturnsDebugForDebug() { + assertEquals('D', LogHelper.getLogLevelChar(3)) + } + + @Test + fun getLogLevelCharReturnsInfoForInfo() { + assertEquals('I', LogHelper.getLogLevelChar(4)) + } + + @Test + fun getLogLevelCharReturnsVerboseForUnknownLevel() { + assertEquals('V', LogHelper.getLogLevelChar(Int.MAX_VALUE)) + } + + @Test + fun getGenericLogStringFormatsLevelTagAndMessage() { + val log = LogHelper.getGenericLogString(5, "Network", "Timeout") + + assertEquals("W/Network: Timeout", log) + } + + @Test + fun addExternalLogForwardsDebugMessagesToExternalLogger() { + val logger = mock() + LogHelper.addExternalLog(logger) + + LogHelper.d("UnitTest", "hello") + + verify(logger).handleExtraLogging(3, "UnitTest", "hello") + } +} + + diff --git a/helperlib/src/test/java/com/itachi1706/helperlib/helpers/ValidationHelperTest.kt b/helperlib/src/test/java/com/itachi1706/helperlib/helpers/ValidationHelperTest.kt new file mode 100644 index 0000000..860b134 --- /dev/null +++ b/helperlib/src/test/java/com/itachi1706/helperlib/helpers/ValidationHelperTest.kt @@ -0,0 +1,34 @@ +package com.itachi1706.helperlib.helpers + +import kotlin.test.Test +import kotlin.test.assertEquals + +class ValidationHelperTest { + + @Test + fun bytesToHexFormatsSingleByteAsUppercaseHex() { + val value = byteArrayOf(0x0A) + + assertEquals("0A", ValidationHelper.bytesToHex(value)) + } + + @Test + fun bytesToHexFormatsMultipleBytesWithColonSeparator() { + val value = byteArrayOf(0x12, 0x34, 0x56) + + assertEquals("12:34:56", ValidationHelper.bytesToHex(value)) + } + + @Test + fun bytesToHexHandlesNegativeByteValuesCorrectly() { + val value = byteArrayOf(0xFF.toByte(), 0x80.toByte(), 0x00) + + assertEquals("FF:80:00", ValidationHelper.bytesToHex(value)) + } + + @Test + fun bytesToHexReturnsEmptyStringForEmptyArray() { + assertEquals("", ValidationHelper.bytesToHex(byteArrayOf())) + } +} + diff --git a/helperlib/src/test/java/com/itachi1706/helperlib/objects/ApiResponseTest.kt b/helperlib/src/test/java/com/itachi1706/helperlib/objects/ApiResponseTest.kt new file mode 100644 index 0000000..3dfd898 --- /dev/null +++ b/helperlib/src/test/java/com/itachi1706/helperlib/objects/ApiResponseTest.kt @@ -0,0 +1,136 @@ +package com.itachi1706.helperlib.objects + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class ApiResponseTest { + + @Test + fun getTypedDataReturnsDecodedObjectWhenDataMatchesType() { + val response = ApiResponse( + date = "2025-10-01T10:29:38.201645045Z", + status = 200, + message = "OK", + success = true, + data = buildJsonObject { + put("id", 7) + put("name", "Kenneth") + } + ) + + val typedData = response.getTypedData() + + assertEquals(TestPayload(id = 7, name = "Kenneth"), typedData) + } + + @Test + fun getTypedDataReturnsNullWhenDataIsMissing() { + val response = ApiResponse( + date = "2025-10-01T10:29:38.201645045Z", + status = 204, + message = "No Content", + success = true, + data = null + ) + + assertNull(response.getTypedData()) + } + + @Test + fun getTypedDataUsesProvidedJsonConfigurationForUnknownKeys() { + val response = ApiResponse( + date = "2025-10-01T10:29:38.201645045Z", + status = 200, + message = "OK", + success = true, + data = buildJsonObject { + put("id", 7) + put("name", "Kenneth") + put("ignored", "value") + } + ) + + val typedData = response.getTypedData( + Json { + ignoreUnknownKeys = true + } + ) + + assertEquals(TestPayload(id = 7, name = "Kenneth"), typedData) + } + + @Test + fun getTypedDataUsesProvidedJsonConfigurationForLenientPayloads() { + val json = Json { + isLenient = true + ignoreUnknownKeys = true + } + + val response = json.decodeFromString( + "{\"date\":\"2025-10-01T10:29:38.201645045Z\",\"status\":200,\"message\":\"OK\",\"success\":true,\"data\":{\"id\":7,\"name\":\"Kenneth\"},\"ignored\":\"value\"}" + ) + + val typedData = response.getTypedData(json) + + assertEquals(TestPayload(id = 7, name = "Kenneth"), typedData) + } + + @Test + fun getTypedDataThrowsWhenPayloadDoesNotMatchRequestedType() { + val response = ApiResponse( + date = "2025-10-01T10:29:38.201645045Z", + status = 200, + message = "OK", + success = true, + data = JsonPrimitive("not a number") + ) + + assertFailsWith { + response.getTypedData() + } + } + + @Test + fun getDateObjectParsesIsoDateWithNanosecondsByTruncatingToMilliseconds() { + val response = ApiResponse( + date = "2025-10-01T10:29:38.201645045Z", + status = 200, + message = "OK", + success = true + ) + + val parsedDate = response.getDateObject() + + assertNotNull(parsedDate) + assertEquals(1759314578201L, parsedDate.time) + } + + @Test + fun getDateObjectReturnsNullForInvalidDateString() { + val response = ApiResponse( + date = "not-a-date", + status = 500, + message = "Error", + success = false + ) + + assertNull(response.getDateObject()) + } + + @Serializable + private data class TestPayload( + val id: Int, + val name: String + ) +} + + + diff --git a/helperlib/src/test/java/com/itachi1706/helperlib/objects/HttpRequestTest.kt b/helperlib/src/test/java/com/itachi1706/helperlib/objects/HttpRequestTest.kt new file mode 100644 index 0000000..899db51 --- /dev/null +++ b/helperlib/src/test/java/com/itachi1706/helperlib/objects/HttpRequestTest.kt @@ -0,0 +1,49 @@ +package com.itachi1706.helperlib.objects + +import com.android.volley.Request +import kotlin.test.Test +import kotlin.test.assertEquals + +class HttpRequestTest { + + @Test + fun mapToVolleyMethodsReturnsGetForGet() { + assertEquals(Request.Method.GET, HttpRequest.mapToVolleyMethods(HttpRequest.Method.GET)) + } + + @Test + fun mapToVolleyMethodsReturnsPostForPost() { + assertEquals(Request.Method.POST, HttpRequest.mapToVolleyMethods(HttpRequest.Method.POST)) + } + + @Test + fun mapToVolleyMethodsReturnsPutForPut() { + assertEquals(Request.Method.PUT, HttpRequest.mapToVolleyMethods(HttpRequest.Method.PUT)) + } + + @Test + fun mapToVolleyMethodsReturnsDeleteForDelete() { + assertEquals(Request.Method.DELETE, HttpRequest.mapToVolleyMethods(HttpRequest.Method.DELETE)) + } + + @Test + fun mapToVolleyMethodsReturnsPatchForPatch() { + assertEquals(Request.Method.PATCH, HttpRequest.mapToVolleyMethods(HttpRequest.Method.PATCH)) + } + + @Test + fun mapToVolleyMethodsReturnsHeadForHead() { + assertEquals(Request.Method.HEAD, HttpRequest.mapToVolleyMethods(HttpRequest.Method.HEAD)) + } + + @Test + fun mapToVolleyMethodsReturnsOptionsForOptions() { + assertEquals(Request.Method.OPTIONS, HttpRequest.mapToVolleyMethods(HttpRequest.Method.OPTIONS)) + } + + @Test + fun mapToVolleyMethodsReturnsTraceForTrace() { + assertEquals(Request.Method.TRACE, HttpRequest.mapToVolleyMethods(HttpRequest.Method.TRACE)) + } +} +