How to Test Push Notifications on Mobile Apps (Complete Guide)
Push notifications bridge your server and your user. They have to be fast, they have to be reliable, they have to land on the right device, and they have to do the right thing when tapped. Testing the
Push notifications bridge your server and your user. They have to be fast, they have to be reliable, they have to land on the right device, and they have to do the right thing when tapped. Testing them thoroughly takes more than "send a test message and see if it shows up."
Why push testing is tricky
Three separate systems: your backend, FCM (Android) or APNs (iOS), and the client. Each introduces its own failure modes. Delivery is best-effort — there is no guarantee. Payloads have size limits. Badges, sounds, categories, and actions all interact in non-obvious ways. The permission model changed twice in the last three years on each platform.
What to test
Permission flows
- First-launch permission prompt — shown at the right moment (not on cold start)
- User denies permission — app handles gracefully, offers setting re-prompt later
- User revokes in OS settings — app detects and surfaces CTA to re-enable
- Android 13+ POST_NOTIFICATIONS permission — runtime request, correct category
- iOS provisional authorization — quiet notifications delivered without prompt
Delivery — happy path
- Foreground — notification displays in-app banner (not system tray)
- Background — notification appears in system tray
- App killed — notification still delivered
- Badge count updates on app icon
- Sound plays for high-priority categories; silent for low
Content
- Title and body render correctly (no unescaped variables like
{username}) - Emojis render
- Localization — notification is in user's locale, not always English
- Long text truncates with "more" option
- Rich content (image, big text, media) displays correctly
- Notification groups (summary + details) collapse properly
Tap actions
- Tap opens the app to the right screen (deep link)
- Tap with app in foreground — correct behavior (usually no action or in-app navigation)
- Tap with app killed — cold start navigates to target screen, not home
- Notification action buttons (Reply, Mark Read) work without opening app
- Dismiss action clears badge correctly
Edge cases
- Multiple notifications — grouped on Android 11+, threaded on iOS
- Delivery while in Do Not Disturb mode — respects mode, queues as appropriate
- Delivery with device offline — queued by FCM/APNs, delivered on reconnect
- User logs out — pending notifications canceled or marked stale
- User switches account — notifications from old account do not leak
- Multi-device account — notification logic (all devices, primary only?)
- Priority levels (high/normal) — high bypasses battery saver, normal does not
Security
- Notification content does not leak sensitive data to lock screen
- Notification payload does not include PII when user opted into minimal content
- Deep link validation — malicious notification cannot navigate to unauthenticated deep link
- FCM/APNs token rotation handled — old token invalidated on server
Accessibility
- Notification announced by screen reader
- Action buttons accessible via assistive tech
- Haptic feedback respects accessibility preferences
Manual testing approach
Use a dedicated test account. Physical devices only — emulator push handling differs subtly. Test on Android 8, 11, 13, and 14 (permission model changed). Test on iOS 15 and 17.
Send notifications manually via Firebase Console or Apple's notification console. For programmatic testing, have your backend push via FCM Admin SDK / APNs HTTP/2 API directly.
Automated testing
FCM / APNs send is outside your app. What you can automate:
The client side
Use a local push simulator:
// Inject a fake FCM message into the client
val fakeMessage = RemoteMessage.Builder("test")
.addData("title", "Test")
.addData("body", "Test body")
.addData("deeplink", "/orders/123")
.build()
FirebaseMessaging.getInstance().handleRemoteMessageIntent(intent)
The deep link
# Launch the app with the notification's deep link intent
driver.execute_script("mobile: deepLink", {"url": "myapp://orders/123"})
End-to-end via FCM
# Send real FCM to a test device from CI
firebase_admin.messaging.send(
firebase_admin.messaging.Message(
token=test_device_token,
notification=firebase_admin.messaging.Notification(title="CI Test", body="Hello"),
data={"deeplink": "/settings"},
)
)
# Wait for delivery (UiAutomator2 can see system tray notifications)
device.wait(Until.hasObject(By.text("CI Test")), 30000)
device.findObject(By.text("CI Test")).click()
# Assert app is on settings screen
How SUSA handles push
SUSA drives in-app notification flows (the post-tap behavior). For push delivery itself, it supports a local notification injection mode — the test harness pushes a simulated FCM message into the app, and SUSA continues exploration from the tap. Deep link deep-drill, badge updates, and state changes are all tracked.
susatest-agent test myapp.apk --inject-push '{"title":"Test","body":"Test","deeplink":"/orders/123"}'
SUSA's persona matters: the impatient persona measures notification-to-screen latency and flags slow deep links. The adversarial persona attempts malformed deep links (path traversal, injection). The accessibility_user persona confirms screen-reader announcement.
Common production bugs
- Deep link routes to wrong screen after cold start — intent consumed by splash, lost before main activity. Fix: pass intent through activity stack correctly.
- Badge count gets stuck — local counter diverges from server. Fix: server pushes canonical badge count in every notification.
- Grouped notifications show wrong sender — group summary uses most-recent metadata, not aggregated. Fix: build group summary from all messages.
- Silent notification wakes app, drains battery — background fetch triggered by silent push loops. Fix: throttle and actually do the work, not just start services.
- Localization broken when OS locale ≠ app locale — backend picks wrong language. Fix: send user's app-locale preference with the token.
Test push every release. The backend changes touching payload structure are the highest-risk for silent regressions.
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free