diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/generation/impl/NotificationGenerationProcessor.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/generation/impl/NotificationGenerationProcessor.kt index a552f0cc1a..d05e907f24 100644 --- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/generation/impl/NotificationGenerationProcessor.kt +++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/generation/impl/NotificationGenerationProcessor.kt @@ -37,6 +37,9 @@ internal class NotificationGenerationProcessor( private val _lifecycleService: INotificationLifecycleService, private val _time: ITime, ) : INotificationGenerationProcessor { + + private val EXTERNAL_CALLBACKS_TIMEOUT get() = 30_000L + override suspend fun processNotificationData( context: Context, androidNotificationId: Int, @@ -67,7 +70,7 @@ internal class NotificationGenerationProcessor( try { val notificationReceivedEvent = NotificationReceivedEvent(context, notification) - withTimeout(30000L) { + withTimeout(EXTERNAL_CALLBACKS_TIMEOUT) { launchOnIO { _lifecycleService.externalRemoteNotificationReceived(notificationReceivedEvent) @@ -100,7 +103,7 @@ internal class NotificationGenerationProcessor( try { val notificationWillDisplayEvent = NotificationWillDisplayEvent(notificationJob.notification) - withTimeout(30000L) { + withTimeout(EXTERNAL_CALLBACKS_TIMEOUT) { launchOnIO { _lifecycleService.externalNotificationWillShowInForeground(notificationWillDisplayEvent) diff --git a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/generation/NotificationGenerationProcessorTests.kt b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/generation/NotificationGenerationProcessorTests.kt index d5b584331a..154b879a11 100644 --- a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/generation/NotificationGenerationProcessorTests.kt +++ b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/generation/NotificationGenerationProcessorTests.kt @@ -1,8 +1,6 @@ package com.onesignal.notifications.internal.generation import android.content.Context -import androidx.test.core.app.ApplicationProvider -import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest import com.onesignal.common.threading.suspendifyOnIO import com.onesignal.debug.LogLevel import com.onesignal.debug.internal.logging.Logging @@ -21,16 +19,19 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.just import io.mockk.mockk +import io.mockk.mockkStatic import io.mockk.runs +import io.mockk.spyk import kotlinx.coroutines.delay import kotlinx.coroutines.withTimeout import org.json.JSONObject -import org.robolectric.annotation.Config // Mocks used by every test in this file private class Mocks { val notificationDisplayer = mockk() + val context = mockk(relaxed = true) + val applicationService = run { val mockApplicationService = AndroidMockHelper.applicationService() @@ -67,16 +68,21 @@ private class Mocks { mockNotificationRepository } - val notificationGenerationProcessor = - NotificationGenerationProcessor( - applicationService, - notificationDisplayer, - MockHelper.configModelStore(), - notificationRepository, - mockk(), - notificationLifecycleService, - MockHelper.time(1111), + val notificationGenerationProcessor = run { + val mock = spyk( + NotificationGenerationProcessor( + applicationService, + notificationDisplayer, + MockHelper.configModelStore(), + notificationRepository, + mockk(), + notificationLifecycleService, + MockHelper.time(1111), + ), recordPrivateCalls = true ) + every { mock getProperty "EXTERNAL_CALLBACKS_TIMEOUT" } answers { 10L } + mock + } val notificationPayload: JSONObject = JSONObject() @@ -89,26 +95,23 @@ private class Mocks { ) } -@Config( - packageName = "com.onesignal.example", - sdk = [26], -) -@RobolectricTest class NotificationGenerationProcessorTests : FunSpec({ beforeAny { Logging.logLevel = LogLevel.NONE + + mockkStatic(android.text.TextUtils::class) + every { android.text.TextUtils.isEmpty(any()) } answers { firstArg()?.isEmpty() ?: true } } test("processNotificationData should set title correctly") { // Given - val context = ApplicationProvider.getApplicationContext() val mocks = Mocks() coEvery { mocks.notificationDisplayer.displayNotification(any()) } returns true coEvery { mocks.notificationLifecycleService.externalRemoteNotificationReceived(any()) } just runs coEvery { mocks.notificationLifecycleService.externalNotificationWillShowInForeground(any()) } just runs // When - mocks.notificationGenerationProcessor.processNotificationData(context, 1, mocks.notificationPayload, false, 1111) + mocks.notificationGenerationProcessor.processNotificationData(mocks.context, 1, mocks.notificationPayload, false, 1111) // Then coVerify(exactly = 1) { @@ -130,14 +133,13 @@ class NotificationGenerationProcessorTests : FunSpec({ test("processNotificationData should restore notification correctly") { // Given - val context = ApplicationProvider.getApplicationContext() val mocks = Mocks() coEvery { mocks.notificationDisplayer.displayNotification(any()) } returns true coEvery { mocks.notificationLifecycleService.externalRemoteNotificationReceived(any()) } just runs coEvery { mocks.notificationLifecycleService.externalNotificationWillShowInForeground(any()) } just runs // When - mocks.notificationGenerationProcessor.processNotificationData(context, 1, mocks.notificationPayload, true, 1111) + mocks.notificationGenerationProcessor.processNotificationData(mocks.context, 1, mocks.notificationPayload, true, 1111) // Then coVerify(exactly = 1) { @@ -156,7 +158,6 @@ class NotificationGenerationProcessorTests : FunSpec({ test("processNotificationData should not display notification when external callback indicates not to") { // Given - val context = ApplicationProvider.getApplicationContext() val mocks = Mocks() coEvery { mocks.notificationLifecycleService.externalRemoteNotificationReceived(any()) } answers { val receivedEvent = firstArg() @@ -164,14 +165,13 @@ class NotificationGenerationProcessorTests : FunSpec({ } // When - mocks.notificationGenerationProcessor.processNotificationData(context, 1, mocks.notificationPayload, false, 1111) + mocks.notificationGenerationProcessor.processNotificationData(mocks.context, 1, mocks.notificationPayload, false, 1111) // Then } test("processNotificationData should display notification when external callback takes longer than 30 seconds") { // Given - val context = ApplicationProvider.getApplicationContext() val mocks = Mocks() coEvery { mocks.notificationDisplayer.displayNotification(any()) } returns true coEvery { mocks.notificationLifecycleService.externalRemoteNotificationReceived(any()) } coAnswers { @@ -179,7 +179,7 @@ class NotificationGenerationProcessorTests : FunSpec({ } // When - mocks.notificationGenerationProcessor.processNotificationData(context, 1, mocks.notificationPayload, true, 1111) + mocks.notificationGenerationProcessor.processNotificationData(mocks.context, 1, mocks.notificationPayload, true, 1111) // Then coVerify(exactly = 1) { @@ -198,7 +198,6 @@ class NotificationGenerationProcessorTests : FunSpec({ test("processNotificationData should not display notification when foreground callback indicates not to") { // Given - val context = ApplicationProvider.getApplicationContext() val mocks = Mocks() coEvery { mocks.notificationLifecycleService.externalRemoteNotificationReceived(any()) } just runs coEvery { mocks.notificationLifecycleService.externalNotificationWillShowInForeground(any()) } answers { @@ -207,14 +206,13 @@ class NotificationGenerationProcessorTests : FunSpec({ } // When - mocks.notificationGenerationProcessor.processNotificationData(context, 1, mocks.notificationPayload, false, 1111) + mocks.notificationGenerationProcessor.processNotificationData(mocks.context, 1, mocks.notificationPayload, false, 1111) // Then } test("processNotificationData should display notification when foreground callback takes longer than 30 seconds") { // Given - val context = ApplicationProvider.getApplicationContext() val mocks = Mocks() coEvery { mocks.notificationDisplayer.displayNotification(any()) } returns true coEvery { mocks.notificationLifecycleService.externalNotificationWillShowInForeground(any()) } coAnswers { @@ -222,7 +220,7 @@ class NotificationGenerationProcessorTests : FunSpec({ } // When - mocks.notificationGenerationProcessor.processNotificationData(context, 1, mocks.notificationPayload, true, 1111) + mocks.notificationGenerationProcessor.processNotificationData(mocks.context, 1, mocks.notificationPayload, true, 1111) // Then coVerify(exactly = 1) { @@ -241,7 +239,6 @@ class NotificationGenerationProcessorTests : FunSpec({ test("processNotificationData should immediately drop the notification when will display callback indicates to") { // Given - val context = ApplicationProvider.getApplicationContext() val mocks = Mocks() coEvery { mocks.notificationLifecycleService.externalRemoteNotificationReceived(any()) } just runs coEvery { mocks.notificationLifecycleService.externalNotificationWillShowInForeground(any()) } answers { @@ -252,13 +249,12 @@ class NotificationGenerationProcessorTests : FunSpec({ // If discard is set to false this should timeout waiting for display() withTimeout(1_000) { - mocks.notificationGenerationProcessor.processNotificationData(context, 1, mocks.notificationPayload, false, 1111) + mocks.notificationGenerationProcessor.processNotificationData(mocks.context, 1, mocks.notificationPayload, false, 1111) } } test("processNotificationData should immediately drop the notification when received event callback indicates to") { // Given - val context = ApplicationProvider.getApplicationContext() val mocks = Mocks() coEvery { mocks.notificationLifecycleService.externalRemoteNotificationReceived(any()) } answers { val receivedEvent = firstArg() @@ -268,13 +264,12 @@ class NotificationGenerationProcessorTests : FunSpec({ // If discard is set to false this should timeout waiting for display() withTimeout(1_000) { - mocks.notificationGenerationProcessor.processNotificationData(context, 1, mocks.notificationPayload, false, 1111) + mocks.notificationGenerationProcessor.processNotificationData(mocks.context, 1, mocks.notificationPayload, false, 1111) } } test("processNotificationData allows the will display callback to prevent default behavior twice") { // Given - val context = ApplicationProvider.getApplicationContext() val mocks = Mocks() coEvery { mocks.notificationDisplayer.displayNotification(any()) } returns true coEvery { mocks.notificationLifecycleService.externalRemoteNotificationReceived(any()) } just runs @@ -290,7 +285,7 @@ class NotificationGenerationProcessorTests : FunSpec({ } // When - mocks.notificationGenerationProcessor.processNotificationData(context, 1, mocks.notificationPayload, false, 1111) + mocks.notificationGenerationProcessor.processNotificationData(mocks.context, 1, mocks.notificationPayload, false, 1111) // Then coVerify(exactly = 0) { @@ -300,7 +295,6 @@ class NotificationGenerationProcessorTests : FunSpec({ test("processNotificationData allows the received event callback to prevent default behavior twice") { // Given - val context = ApplicationProvider.getApplicationContext() val mocks = Mocks() coEvery { mocks.notificationDisplayer.displayNotification(any()) } returns true coEvery { mocks.notificationLifecycleService.externalRemoteNotificationReceived(any()) } coAnswers { @@ -315,7 +309,7 @@ class NotificationGenerationProcessorTests : FunSpec({ } // When - mocks.notificationGenerationProcessor.processNotificationData(context, 1, mocks.notificationPayload, true, 1111) + mocks.notificationGenerationProcessor.processNotificationData(mocks.context, 1, mocks.notificationPayload, true, 1111) // Then coVerify(exactly = 0) { diff --git a/OneSignalSDK/onesignal/testhelpers/src/main/java/com/onesignal/mocks/AndroidMockHelper.kt b/OneSignalSDK/onesignal/testhelpers/src/main/java/com/onesignal/mocks/AndroidMockHelper.kt index 5de7c068d7..265824bee6 100644 --- a/OneSignalSDK/onesignal/testhelpers/src/main/java/com/onesignal/mocks/AndroidMockHelper.kt +++ b/OneSignalSDK/onesignal/testhelpers/src/main/java/com/onesignal/mocks/AndroidMockHelper.kt @@ -1,8 +1,10 @@ package com.onesignal.mocks +import android.content.Context import androidx.test.core.app.ApplicationProvider import com.onesignal.core.internal.application.IApplicationService import io.mockk.every +import io.mockk.mockk /** * Singleton which provides common mock services when running in an Android environment. @@ -11,7 +13,13 @@ object AndroidMockHelper { fun applicationService(): IApplicationService { val mockAppService = MockHelper.applicationService() - every { mockAppService.appContext } returns ApplicationProvider.getApplicationContext() + try { + // Robolectric + every { mockAppService.appContext } returns ApplicationProvider.getApplicationContext() + } catch (_: IllegalStateException) { + // Fallback to simpler mock (using mockk) if Robolectric is not used in the test + every { mockAppService.appContext } returns mockk(relaxed = true) + } return mockAppService }