From 86f4357ba27a0f7cab632a90c61004b1e598890e Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Thu, 20 Nov 2025 21:38:46 -0500 Subject: [PATCH] tests: much faster notification module tests Mocked out timeout for external lifetime event callbacks, this made the tests for notifications take an extra 90 seconds. Also removed Robolectric from this test and replaced it with lighter weight mockk mocks. --- .../impl/NotificationGenerationProcessor.kt | 7 +- .../NotificationGenerationProcessorTests.kt | 68 +++++++++---------- .../com/onesignal/mocks/AndroidMockHelper.kt | 10 ++- 3 files changed, 45 insertions(+), 40 deletions(-) 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 }