From 6c55f3c8ba95c368f2ac859e8a0cddd89b4904e3 Mon Sep 17 00:00:00 2001 From: Rajat <143978428+mrajatttt@users.noreply.github.com> Date: Mon, 12 Aug 2024 11:55:22 -0700 Subject: [PATCH] Added unit tests (#7) * unit testing * Create ci.yml --- .github/workflows/ci.yml | 136 ++++++++++++++++++ chat-sdk/build.gradle.kts | 31 ++-- .../chat/sdk/network/WebSocketManager.kt | 9 +- .../chat/sdk/repository/ChatService.kt | 6 +- .../connect/chat/sdk/ChatSessionImplTest.kt | 84 +++++++++++ .../connect/chat/sdk/ExampleUnitTest.kt | 17 --- .../chat/sdk/network/AWSClientImplTest.kt | 112 +++++++++++++++ .../sdk/repository/ChatServiceImplTest.kt | 121 ++++++++++++++++ chat-sdk/test-summary.gradle.kts | 73 ++++++++++ gradle/libs.versions.toml | 9 ++ 10 files changed, 564 insertions(+), 34 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 chat-sdk/src/test/java/com/amazon/connect/chat/sdk/ChatSessionImplTest.kt delete mode 100644 chat-sdk/src/test/java/com/amazon/connect/chat/sdk/ExampleUnitTest.kt create mode 100644 chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/AWSClientImplTest.kt create mode 100644 chat-sdk/src/test/java/com/amazon/connect/chat/sdk/repository/ChatServiceImplTest.kt create mode 100644 chat-sdk/test-summary.gradle.kts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..be75d2c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,136 @@ +name: Android CI + +on: + pull_request: + branches: [ main ] + push: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up JDK 17 + uses: actions/setup-java@v1 + with: + java-version: 17 + + - name: Set up Android SDK + uses: android-actions/setup-android@v2 + with: + api-level: 30 + build-tools: 30.0.3 + + - name: Cache Gradle + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Build with Gradle (no tests) + run: ./gradlew assemble + + - name: Save Build Artifacts + uses: actions/upload-artifact@v2 + with: + name: app-build + path: app/build/outputs/apk/ + + test: + runs-on: ubuntu-latest + needs: build + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up JDK 17 + uses: actions/setup-java@v1 + with: + java-version: 17 + + - name: Set up Android SDK + uses: android-actions/setup-android@v2 + with: + api-level: 30 + build-tools: 30.0.3 + + - name: Cache Gradle + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Run Unit Tests and Capture Results + id: run_tests + run: | + ./gradlew test | tee test-results.txt + TEST_RESULTS=$(grep -A 7 "Test Summary" test-results.txt | tail -n +2 | sed 's/\x1B\[[0-9;]*[JKmsu]//g' | tr -d '[:space:]') + echo "$TEST_RESULTS" > test-summary.txt + + # - name: Format Test Results for Two-Row Markdown Table with Emojis + # run: | + # echo "### Test Results" > formatted-summary.txt + # echo "" >> formatted-summary.txt + # echo "| Total Tests | Passed | Failed | Skipped | Result |" >> formatted-summary.txt + # echo "|-------------|--------|--------|---------|--------|" >> formatted-summary.txt + + # echo "Raw Test Summary Content:" + # cat test-summary.txt + + # # Extracting values using awk + # TOTAL=$(echo "$TEST_RESULTS" | awk -F'[: ]+' '/TotalTests/ {print $2}') + # PASSED=$(echo "$TEST_RESULTS" | awk -F'[: ]+' '/Passed/ {print $2}') + # FAILED=$(echo "$TEST_RESULTS" | awk -F'[: ]+' '/Failed/ {print $2}') + # SKIPPED=$(echo "$TEST_RESULTS" | awk -F'[: ]+' '/Skipped/ {print $2}') + # RESULT=$(echo "$TEST_RESULTS" | awk -F'[: ]+' '/Result/ {print $2}') + + # # Detailed debugging + # echo "Debug: Extracted Values:" + # echo " TOTAL: '$TOTAL'" + # echo " PASSED: '$PASSED'" + # echo " FAILED: '$FAILED'" + # echo " SKIPPED: '$SKIPPED'" + # echo " RESULT (Raw): '$RESULT'" + + # if [[ "$RESULT" == "SUCCESS" ]]; then + # EMOJI="✅" + # elif [[ "$RESULT" == "FAILURE" ]]; then + # EMOJI="❌" + # else + # EMOJI="⚠️" + # fi + + # echo "Debug: Final RESULT after processing: '$RESULT $EMOJI'" + + # echo "| $TOTAL | $PASSED | $FAILED | $SKIPPED | $RESULT $EMOJI |" >> formatted-summary.txt + + # # Show the final output for debugging + # echo "Final formatted-summary.txt content:" + # cat formatted-summary.txt + + # - name: Comment on PR + # if: github.event_name == 'pull_request' + # uses: actions/github-script@v6 + # with: + # script: | + # const fs = require('fs'); + # const testResults = fs.readFileSync('formatted-summary.txt', 'utf8'); + # github.rest.issues.createComment({ + # issue_number: context.issue.number, + # owner: context.repo.owner, + # repo: context.repo.repo, + # body: testResults + # }); diff --git a/chat-sdk/build.gradle.kts b/chat-sdk/build.gradle.kts index 2723e77..ed193a7 100644 --- a/chat-sdk/build.gradle.kts +++ b/chat-sdk/build.gradle.kts @@ -64,16 +64,7 @@ dependencies { implementation(libs.composeUiGraphics) implementation(libs.composeUiToolingPreview) implementation(libs.material3) - implementation("com.google.android.gms:play-services-basement:18.2.0") implementation(libs.runtimeLivedata) - implementation(libs.lifecycleProcess) // Add this dependency in libs.versions.toml if necessary - testImplementation(libs.junit) - androidTestImplementation(libs.androidxJunit) - androidTestImplementation(libs.espressoCore) - androidTestImplementation(platform(libs.composeBom)) - androidTestImplementation(libs.composeUiTestJunit4) - debugImplementation(libs.composeUiTooling) - debugImplementation(libs.composeUiTestManifest) // Lifecycle livedata implementation(libs.lifecycleLivedataKtx) @@ -91,6 +82,7 @@ dependencies { //Hilt implementation(libs.hiltAndroid) implementation(libs.hiltNavigationCompose) + implementation(libs.lifecycleProcess) kapt(libs.hiltCompiler) implementation(libs.navigationCompose) kapt(libs.hiltAndroidCompiler) @@ -105,6 +97,24 @@ dependencies { // Image loading implementation(libs.coilCompose) + // Testing + // Mockito for mocking + testImplementation(libs.mockito.core) + testImplementation(libs.mockito.inline) + + // Kotlin extensions for Mockito + testImplementation(libs.mockito.kotlin) + + // Coroutines test library + testImplementation(libs.coroutines.test) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidxJunit) + androidTestImplementation(platform(libs.composeBom)) + androidTestImplementation(libs.composeUiTestJunit4) + debugImplementation(libs.composeUiTooling) + debugImplementation(libs.composeUiTestManifest) + testImplementation(libs.robolectric) } publishing { @@ -129,3 +139,6 @@ publishing { tasks.withType().configureEach { dependsOn(tasks.named("assembleRelease")) } + +// Test summary gradle file +apply(from = "test-summary.gradle.kts") \ No newline at end of file diff --git a/chat-sdk/src/main/java/com/amazon/connect/chat/sdk/network/WebSocketManager.kt b/chat-sdk/src/main/java/com/amazon/connect/chat/sdk/network/WebSocketManager.kt index f4c54c7..1e4b1e3 100644 --- a/chat-sdk/src/main/java/com/amazon/connect/chat/sdk/network/WebSocketManager.kt +++ b/chat-sdk/src/main/java/com/amazon/connect/chat/sdk/network/WebSocketManager.kt @@ -5,7 +5,6 @@ import android.net.Network import android.util.Log import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver -import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.ProcessLifecycleOwner import com.amazon.connect.chat.sdk.Config import com.amazon.connect.chat.sdk.model.Message @@ -13,17 +12,17 @@ import com.amazon.connect.chat.sdk.model.MessageType import com.amazon.connect.chat.sdk.model.TranscriptItem import com.amazon.connect.chat.sdk.utils.CommonUtils import com.amazon.connect.chat.sdk.utils.ContentType +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import okhttp3.WebSocket import okhttp3.WebSocketListener -import java.util.concurrent.TimeUnit -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import okio.IOException import org.json.JSONObject +import java.util.concurrent.TimeUnit import javax.inject.Inject object EventTypes { diff --git a/chat-sdk/src/main/java/com/amazon/connect/chat/sdk/repository/ChatService.kt b/chat-sdk/src/main/java/com/amazon/connect/chat/sdk/repository/ChatService.kt index 643289e..e4d6240 100644 --- a/chat-sdk/src/main/java/com/amazon/connect/chat/sdk/repository/ChatService.kt +++ b/chat-sdk/src/main/java/com/amazon/connect/chat/sdk/repository/ChatService.kt @@ -20,7 +20,7 @@ interface ChatService { * Disconnects the current chat session. * @return A Result indicating whether the disconnection was successful. */ - suspend fun disconnectChatSession(): Result + suspend fun disconnectChatSession(): Result } class ChatServiceImpl @Inject constructor( @@ -44,13 +44,13 @@ class ChatServiceImpl @Inject constructor( } } - override suspend fun disconnectChatSession(): Result { + override suspend fun disconnectChatSession(): Result { return runCatching { val connectionDetails = connectionDetailsProvider.getConnectionDetails() ?: throw Exception("No connection details available") awsClient.disconnectParticipantConnection(connectionDetails.connectionToken).getOrThrow() Log.d("ChatServiceImpl", "Participant Disconnected") - Unit + true }.onFailure { exception -> Log.e("ChatServiceImpl", "Failed to disconnect participant: ${exception.message}", exception) } diff --git a/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/ChatSessionImplTest.kt b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/ChatSessionImplTest.kt new file mode 100644 index 0000000..025da35 --- /dev/null +++ b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/ChatSessionImplTest.kt @@ -0,0 +1,84 @@ +package com.amazon.connect.chat.sdk + +import com.amazon.connect.chat.sdk.model.ChatDetails +import com.amazon.connect.chat.sdk.model.GlobalConfig +import com.amazon.connect.chat.sdk.repository.ChatService +import com.amazonaws.regions.Regions +import junit.framework.TestCase.assertTrue +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.* +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.verify +import org.robolectric.RobolectricTestRunner + + +@ExperimentalCoroutinesApi +@RunWith(RobolectricTestRunner::class) +class ChatSessionImplTest { + + @Mock + private lateinit var chatService: ChatService + + private lateinit var chatSession: ChatSession + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + chatSession = ChatSessionImpl(chatService) + } + + @Test + fun test_configure(){ + val config = GlobalConfig(region = Regions.US_WEST_2) + chatSession.configure(config) + verify(chatService).configure(config) + } + + @Test + fun test_connect_success() = runTest { + val chatDetails = ChatDetails(participantToken = "participant-token") + `when`(chatService.createChatSession(chatDetails)).thenReturn(Result.success(true)) + + val result = chatSession.connect(chatDetails) + + assertTrue(result.isSuccess) + verify(chatService).createChatSession(chatDetails) + } + + @Test + fun test_connect_failure() = runTest { + val chatDetails = ChatDetails(participantToken = "invalid token") + `when`(chatService.createChatSession(chatDetails)).thenThrow(RuntimeException("Network error")) + + val result = chatSession.connect(chatDetails) + + assertTrue(result.isFailure) + verify(chatService).createChatSession(chatDetails) + } + + @Test + fun test_disconnect_success() = runTest { + `when`(chatService.disconnectChatSession()).thenReturn(Result.success(true)) + + val result = chatSession.disconnect() + + assertTrue(result.isSuccess) + verify(chatService).disconnectChatSession() + } + + @Test + fun test_disconnect_failure() = runTest { + `when`(chatService.disconnectChatSession()).thenThrow(RuntimeException("Network error")) + + val result = chatSession.disconnect() + + assertTrue(result.isFailure) + verify(chatService).disconnectChatSession() + } + +} diff --git a/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/ExampleUnitTest.kt b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/ExampleUnitTest.kt deleted file mode 100644 index 9d7f613..0000000 --- a/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.amazon.connect.chat.sdk - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file diff --git a/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/AWSClientImplTest.kt b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/AWSClientImplTest.kt new file mode 100644 index 0000000..27e2de3 --- /dev/null +++ b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/AWSClientImplTest.kt @@ -0,0 +1,112 @@ +package com.amazon.connect.chat.sdk.network + +import com.amazon.connect.chat.sdk.model.GlobalConfig +import com.amazonaws.regions.Region +import com.amazonaws.regions.Regions +import com.amazonaws.services.connectparticipant.AmazonConnectParticipantClient +import com.amazonaws.services.connectparticipant.model.ConnectionCredentials +import com.amazonaws.services.connectparticipant.model.CreateParticipantConnectionRequest +import com.amazonaws.services.connectparticipant.model.CreateParticipantConnectionResult +import com.amazonaws.services.connectparticipant.model.DisconnectParticipantRequest +import com.amazonaws.services.connectparticipant.model.DisconnectParticipantResult +import com.amazonaws.services.connectparticipant.model.Websocket +import junit.framework.TestCase.assertTrue +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner + +@ExperimentalCoroutinesApi +@RunWith(RobolectricTestRunner::class) +class AWSClientImplTest { + + @Mock + private lateinit var mockClient: AmazonConnectParticipantClient + + private lateinit var awsClient: AWSClientImpl + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + awsClient = AWSClientImpl(mockClient) + } + + @Test + fun test_configure() { + val config = GlobalConfig(region = Regions.US_WEST_2) + awsClient.configure(config) + verify(mockClient).setRegion(Region.getRegion(config.region)) + } + + @Test + fun test_createParticipantConnection_success() = runTest { + val participantToken = "token" + + val mockResponse = CreateParticipantConnectionResult().apply { + connectionCredentials = ConnectionCredentials().apply { + connectionToken = "mockedConnectionToken" + } + websocket = Websocket().apply { + url = "mockedWebsocketUrl" + connectionExpiry = "mockedExpiryTime" + } + } + + `when`(mockClient.createParticipantConnection(any(CreateParticipantConnectionRequest::class.java))) + .thenReturn(mockResponse) + + val result = awsClient.createParticipantConnection(participantToken) + + assertTrue("Expected successful connection creation", result.isSuccess) + verify(mockClient).createParticipantConnection(any(CreateParticipantConnectionRequest::class.java)) + } + + @Test + fun test_createParticipantConnection_failure() = runTest { + val participantToken = "invalid_token" + `when`(mockClient.createParticipantConnection(any(CreateParticipantConnectionRequest::class.java))) + .thenThrow(RuntimeException("Network error")) + + try { + awsClient.createParticipantConnection(participantToken) + } catch (e: Exception) { + assertTrue("Expected exception due to network error", e is RuntimeException) + assertTrue("Expected network error message", e.message == "Network error") + } + } + + @Test + fun test_disconnectParticipantConnection_success() = runTest { + val connectionToken = "token" + val mockResponse = mock(DisconnectParticipantResult::class.java) + `when`(mockClient.disconnectParticipant(any(DisconnectParticipantRequest::class.java))) + .thenReturn(mockResponse) + + val result = awsClient.disconnectParticipantConnection(connectionToken) + + assertTrue("Expected successful disconnection", result.isSuccess) + verify(mockClient).disconnectParticipant(any(DisconnectParticipantRequest::class.java)) + } + + @Test + fun test_disconnectParticipantConnection_failure() = runTest { + val connectionToken = "invalid_token" + `when`(mockClient.disconnectParticipant(any(DisconnectParticipantRequest::class.java))) + .thenThrow(RuntimeException("Network error")) + + try { + awsClient.disconnectParticipantConnection(connectionToken) + } catch (e: Exception) { + assertTrue("Expected exception due to network error", e is RuntimeException) + assertTrue("Expected network error message", e.message == "Network error") + } + } +} diff --git a/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/repository/ChatServiceImplTest.kt b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/repository/ChatServiceImplTest.kt new file mode 100644 index 0000000..eee0906 --- /dev/null +++ b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/repository/ChatServiceImplTest.kt @@ -0,0 +1,121 @@ +package com.amazon.connect.chat.sdk.repository + +import com.amazon.connect.chat.sdk.model.ChatDetails +import com.amazon.connect.chat.sdk.model.ConnectionDetails +import com.amazon.connect.chat.sdk.model.GlobalConfig +import com.amazon.connect.chat.sdk.network.APIClient +import com.amazon.connect.chat.sdk.network.AWSClient +import com.amazonaws.regions.Regions +import com.amazonaws.services.connectparticipant.model.DisconnectParticipantResult +import junit.framework.TestCase.assertTrue +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.* +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner + +@ExperimentalCoroutinesApi +@RunWith(RobolectricTestRunner::class) +class ChatServiceImplTest { + + @Mock + private lateinit var apiClient: APIClient + + @Mock + private lateinit var awsClient: AWSClient + + @Mock + private lateinit var connectionDetailsProvider: ConnectionDetailsProvider + + private lateinit var chatService: ChatService + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + chatService = ChatServiceImpl(apiClient, awsClient, connectionDetailsProvider) + } + + @Test + fun test_configure(){ + val config = GlobalConfig(region = Regions.US_WEST_2) + chatService.configure(config) + verify(awsClient).configure(config) + } + + @Test + fun test_createParticipantConnection_success() = runTest { + val chatDetails = ChatDetails(participantToken = "token") + val mockConnectionDetails = createMockConnectionDetails("valid_token") + + `when`(awsClient.createParticipantConnection(chatDetails.participantToken)).thenReturn(Result.success(mockConnectionDetails)) + `when`(connectionDetailsProvider.updateChatDetails(chatDetails)).then { /**/ } + + val result = chatService.createChatSession(chatDetails) + + assertTrue(result.isSuccess) + verify(connectionDetailsProvider).updateChatDetails(chatDetails) + verify(connectionDetailsProvider).updateConnectionDetails(mockConnectionDetails) + verify(awsClient).createParticipantConnection(chatDetails.participantToken) + } + + @Test + fun test_createParticipantConnection_failure() = runTest { + val chatDetails = ChatDetails(participantToken = "invalid_token") + `when`(awsClient.createParticipantConnection(chatDetails.participantToken)).thenReturn( + Result.failure(Exception("Network error")) + ) + val result = chatService.createChatSession(chatDetails) + assertTrue(result.isFailure) + verify(connectionDetailsProvider).updateChatDetails(chatDetails) + verify(awsClient).createParticipantConnection(chatDetails.participantToken) + } + + @Test + fun test_disconnectParticipantConnection_success() = runTest { + val mockConnectionDetails = createMockConnectionDetails("valid_token") + `when`(connectionDetailsProvider.getConnectionDetails()).thenReturn(mockConnectionDetails) + `when`(awsClient.disconnectParticipantConnection(mockConnectionDetails.connectionToken)).thenReturn(Result.success( + DisconnectParticipantResult() + )) + + val result = chatService.disconnectChatSession() + + assertTrue(result.isSuccess) + verify(connectionDetailsProvider).getConnectionDetails() + verify(awsClient).disconnectParticipantConnection(mockConnectionDetails.connectionToken) + } + + @Test + fun test_disconnectParticipantConnection_failure() = runTest { + val mockConnectionDetails = createMockConnectionDetails("invalid_token") + `when`(connectionDetailsProvider.getConnectionDetails()).thenReturn(mockConnectionDetails) + `when`(awsClient.disconnectParticipantConnection(mockConnectionDetails.connectionToken)).thenThrow(RuntimeException("Network error")) + + val result = chatService.disconnectChatSession() + assertTrue(result.isFailure) + verify(connectionDetailsProvider).getConnectionDetails() + verify(awsClient).disconnectParticipantConnection(mockConnectionDetails.connectionToken) + } + + @Test + fun test_disconnectParticipantConnection_noConnectionDetails() = runTest { + `when`(connectionDetailsProvider.getConnectionDetails()).thenReturn(null) + val result = chatService.disconnectChatSession() + assertTrue(result.isFailure) + verify(connectionDetailsProvider).getConnectionDetails() + verify(awsClient, never()).disconnectParticipantConnection(anyString()) + } + + private fun createMockConnectionDetails(token : String): ConnectionDetails { + return ConnectionDetails( + connectionToken = token, + websocketUrl = "mockedWebsocketUrl", + expiry = "mockedExpiryTime" + ) + } + +} \ No newline at end of file diff --git a/chat-sdk/test-summary.gradle.kts b/chat-sdk/test-summary.gradle.kts new file mode 100644 index 0000000..ece30ef --- /dev/null +++ b/chat-sdk/test-summary.gradle.kts @@ -0,0 +1,73 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent +import org.gradle.api.tasks.testing.TestDescriptor +import org.gradle.api.tasks.testing.TestResult + + +tasks.withType { + testLogging { + events = setOf( + TestLogEvent.FAILED, + TestLogEvent.PASSED, + TestLogEvent.SKIPPED, + TestLogEvent.STANDARD_ERROR, + TestLogEvent.STANDARD_OUT + ) + exceptionFormat = TestExceptionFormat.FULL + } + + afterSuite(KotlinClosure({ desc, result -> + if (desc.parent == null) { // Will match the outermost suite + val totalTests = result.testCount + val passedTests = result.successfulTestCount + val failedTests = result.failedTestCount + val skippedTests = result.skippedTestCount + val resultType = result.resultType + + val output = """ + + + ${Color.CYAN}─────────────────────────────────────────────────────────────────────────────────${Color.NONE} + ${Color.CYAN}| ${Color.WHITE}Test Summary${Color.CYAN} | + ${Color.CYAN}|───────────────────────────────────────────────────────────────────────────────|${Color.NONE} + ${Color.CYAN}| ${Color.WHITE}Total Tests : ${Color.GREEN}$totalTests${Color.CYAN} | + ${Color.CYAN}| ${Color.WHITE}Passed : ${Color.GREEN}$passedTests${Color.CYAN} | + ${Color.CYAN}| ${Color.WHITE}Failed : ${Color.RED}$failedTests${Color.CYAN} | + ${Color.CYAN}| ${Color.WHITE}Skipped : ${Color.YELLOW}$skippedTests${Color.CYAN} | + ${Color.CYAN}| ${Color.WHITE}Result : ${when (resultType) { + TestResult.ResultType.SUCCESS -> "${Color.GREEN}$resultType" + TestResult.ResultType.FAILURE -> "${Color.RED}$resultType" + TestResult.ResultType.SKIPPED -> "${Color.YELLOW}$resultType" + }}${Color.CYAN} | + ${Color.CYAN}─────────────────────────────────────────────────────────────────────────────────${Color.NONE} + """.trimIndent() + println(output) + } + }, this)) +} + +// Helper class to convert Groovy closure to Kotlin lambda +class KotlinClosure( + private val function: (T1, T2) -> R, + private val owner: Any? = null, + private val thisObject: Any? = null +) : groovy.lang.Closure<@UnsafeVariance R>(owner, thisObject) { + @Suppress("unused") + fun doCall(var1: T1, var2: T2): R = function(var1, var2) +} + +internal enum class Color(val ansiCode: String) { + NONE("\u001B[0m"), + BLACK("\u001B[30m"), + RED("\u001B[31m"), + GREEN("\u001B[32m"), + YELLOW("\u001B[33m"), + BLUE("\u001B[34m"), + PURPLE("\u001B[35m"), + CYAN("\u001B[36m"), + WHITE("\u001B[37m"); + + override fun toString(): String { + return ansiCode + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9e2dfd2..d690aef 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,6 +3,7 @@ androidGradlePlugin = "8.5.1" hiltAndroid = "2.49" kotlin = "1.9.20" hilt = "2.49" +robolectric = "4.13" serialization = "1.9.20" androidxCoreKtx = "1.13.1" androidxLifecycleRuntimeKtx = "2.8.4" @@ -35,6 +36,9 @@ chatSdk = "1.0.0" runtimeLivedata = "1.6.8" appcompat = "1.7.0" material = "1.12.0" +mockito = "4.0.0" +mockitoKotlin = "4.0.0" +coroutinesTest = "1.5.2" lifecycleProcess = "2.8.4" [libraries] @@ -71,6 +75,7 @@ hiltNavigationCompose = { module = "androidx.hilt:hilt-navigation-compose", vers navigationCompose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } awsSdkCore = { module = "com.amazonaws:aws-android-sdk-core", version.ref = "awsSdkCore" } awsSdkConnectParticipant = { module = "com.amazonaws:aws-android-sdk-connectparticipant", version.ref = "awsSdkConnectParticipant" } +robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } serializationJson = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serializationJson" } coilCompose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" } chatSdk = { module = "com.amazon.connect.chat:library", version.ref = "chatSdk" } @@ -78,6 +83,10 @@ runtimeLivedata = { group = "androidx.compose.runtime", name = "runtime-livedata appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } lifecycleProcess = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycleProcess" } +mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" } +mockito-inline = { group = "org.mockito", name = "mockito-inline", version.ref = "mockito" } +mockito-kotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version.ref = "mockitoKotlin" } +coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutinesTest" } [plugins] androidApplication = { id = "com.android.application", version.ref = "androidGradlePlugin" }