How to Test Push Notifications on Android (Complete Guide)
Push notifications are a direct line from an app to its users. When they work, they drive engagement, convey timely information, and can even be a revenue channel. When they fail, users miss critical
Why Push Notification Testing Matters
Push notifications are a direct line from an app to its users. When they work, they drive engagement, convey timely information, and can even be a revenue channel. When they fail, users miss critical alerts, perceive the app as unreliable, and may disable notifications altogether—or uninstall the app.
Impact on User Experience
A notification that appears late, shows garbled text, or lacks a usable action creates friction. Users expect immediacy; a delay of even a few seconds can cause them to act on stale information (e.g., a flash‑sale alert that arrives after the sale ends). Poorly formatted notifications break accessibility expectations: TalkBack users rely on concise, descriptive content; missing content labels or low contrast make the notification unreadable.
Business Risks
Missed notifications translate to lost opportunities. For e‑commerce, a failed cart‑abandonment reminder can mean a dropped sale. For finance apps, a delayed fraud alert can expose users to risk and increase liability. From a compliance standpoint, regulations such as GDPR require that personal data not be inadvertently exposed in notification content; a bug that leaks a token or email in the notification tray can trigger fines and damage brand reputation.
Compliance and Security
Push notifications travel through Firebase Cloud Messaging (FCM) or alternative push services. The payload is often encrypted in transit, but the notification itself is displayed by the system UI. If the app places personally identifiable information (PII) in the title or text fields, that data becomes visible on the lock screen or in the notification shade, violating privacy expectations. Testing must therefore verify that no sensitive data leaks and that the app honors user‑granted permission states (e.g., notification disabled, Do Not Disturb).
---
Test Matrix for Push Notifications
A comprehensive matrix helps you ensure that every relevant dimension is exercised. Below is a table that groups test cases by category, description, expected outcome, and pass/fail criteria.
| ID | Category | Scenario | Description | Expected Result | Pass/Fail Criteria |
|---|---|---|---|---|---|
| H1 | Happy Path | Foreground delivery | App is in foreground, FCM sends a simple data‑only message. | App receives message via FirebaseMessagingService.onMessageReceived and updates UI accordingly. | UI reflects new data within 2 seconds; no crash. |
| H2 | Happy Path | Background notification | App is in background, FCM sends a notification payload with title, text, and icon. | System shows notification in shade; tapping opens the designated Activity. | Notification appears with correct title/text/icon; tapping launches correct screen; back stack behaves as defined. |
| H3 | Happy Path | Action button | Notification includes two action buttons (e.g., “Reply”, “Dismiss”). | Tapping each button triggers the corresponding BroadcastReceiver or Service. | Correct receiver logs action; UI updates if needed; no ANR. |
| H4 | Error Path | Invalid payload | FCM sends a message with malformed JSON (missing to field). | FCM discards message; app receives no callback. | No crash; log shows FCM error; app state unchanged. |
| H5 | Error Path | Exceeding size limit | Payload > 4 KB (FCM limit). | FCM rejects message; device receives nothing. | No notification appears; logs show MessagingError. |
| H6 | Error Path | Missing channel (Android 8.0+) | Notification posted without a valid notification channel ID. | Notification is silently dropped (post‑Oreo). | No notification shade entry; log warns about missing channel. |
| H7 | Edge Case | Doze mode | Device is idle, Doze activated; FCM sends high‑priority message. | System delivers notification within a short grace period; app can show heads‑up if allowed. | Notification appears within ≤ 30 seconds; no excessive battery drain. |
| H8 | Edge Case | Battery optimization whitelist exemption removed | User disables exemption for the app; app attempts to post a notification while in background. | Notification still delivered but may be delayed; app must handle deferral gracefully. | Notification eventually appears; no crash; app logs deferral if using WorkManager. |
| H9 | Edge Case | Multiple channels | App defines three channels (Promotions, Reminders, Alerts) with different importance levels. | Each channel respects its importance setting (e.g., Alerts heads‑up, Promotions silent). | Verify importance via NotificationManager.getNotificationChannel; observe UI behavior. |
| H10 | Edge Case | Notification grouping | App sends five messages with same group key; system should collapse them. | Notification shade shows a single grouped entry; expanding reveals individual messages. | Group summary displays correct count; expanding shows each child. |
| H11 | Accessibility | TalkBack navigation | Notification posted; user explores with TalkBack. | TalkBack reads title, text, and action labels correctly. | No missing contentDescription; spoken text matches visible content. |
| H12 | Accessibility | Contrast | Notification uses low‑contrast text on icon background. | Text must meet WCAG AA contrast ratio (≥ 4.5:1). | Use automated contrast checker; flag if ratio < 4.5. |
| H13 | Security/Privacy | Sensitive data in payload | FCM message includes user email in title field. | Email must not appear in notification shade (should be filtered or omitted). | Verify notification text does not contain email; ensure app strips PII before posting. |
| H14 | Security/Privacy | Replay attack simulation | Capture a valid FCM token and re‑send old notification payload. | App should detect stale nonce or timestamp and ignore replay. | No duplicate UI update; log shows rejection. |
| H15 | Security/Privacy | Permission revoked mid‑session | User disables notifications in system settings while app is running. | Subsequent FCM messages result in no notification; app should handle gracefully. | No notification appears; app logs REMOTE_MESSAGE_RECEIVED but no posting. |
| H16 | Localization | Right‑to‑left language | Device locale set to Arabic; notification includes mixed LTR/RTL text. | Layout respects RTL direction; punctuation positioned correctly. | Verify layout direction via View.getLayoutDirection; visual inspection. |
| H17 | Performance | Rapid burst | App sends 50 notifications in 2 seconds via FCM. | System batches or throttles; UI remains responsive. | No dropped frames; ANR watchdog does not trigger. |
| H18 | Fallback | FCM token refresh | Simulate token expiration; FCM returns new token. | App registers new token with backend; continues to receive messages. | Backend receives updated token; no gap in message delivery. |
*How to use the matrix*: Treat each row as a test case. Automate the verifiable assertions (UI state, logs, backend calls) and run them on a matrix of devices (different API levels, OEM skins, battery‑optimization settings).
---
Manual Testing Approach
Manual testing remains valuable for exploratory checks, especially when validating edge cases that depend on device state or user interaction. Below is a step‑by‑step procedure you can follow on a physical device or emulator.
Environment Setup
- Enable Developer Options – Tap *Settings → About phone → Build number* seven times.
- USB Debugging – Turn on *Developer options → USB debugging*.
- Install the app – Use
adb install -r app.apkto ensure a clean install. - Grant notification permission – On Android 13+, go to *Settings → Apps → → Notifications* and enable the channel(s) you plan to test.
- Clear existing notifications – Swipe away all notifications or run
adb shell cmd notification cancelall.
Using ADB to Send Notifications
You can simulate FCM downstream messages without a backend by using the adb shell cmd notification command or by pushing a raw payload via firebase CLI.
Simple notification via ADB
adb shell cmd notification post -S bigtext \
-t 'Test Title' \
--es 'msg' 'Hello from ADB' \
com.example.myapp
-S bigtextselects a style; you can also use-S inboxfor multiple lines.--esadds a string extra that yourFirebaseMessagingServicecan read if you also send a data payload via FCM.
Sending a data‑only message (requires FCM)
If you have a test server that can call the FCM HTTP v1 API, issue:
curl -X POST -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
-H "Content-Type: application/json" \
-d '{
"message": {
"token": "<DEVICE_FCM_TOKEN>",
"data": {
"score": "850",
"time": "2:45"
}
}
}' \
https://fcm.googleapis.com/v1/projects/<PROJECT_ID>/messages:send
Retrieve the device token with:
adb shell cmd notification list | grep com.example.myapp
or via FirebaseInstanceId.getInstance().getToken() in a debug build.
Verifying Notification Appearance
- Shade inspection – Pull down the shade and confirm title, text, icon, and any action buttons are present.
- Heads‑up vs silent – For high‑importance channels, verify a heads‑up popup appears; for low importance, ensure no interruption.
- Lock screen visibility – Power off the device, wake it, and check whether the notification shows on the lock screen (based on
setVisibility). - Icon adaptation – Ensure the app’s monochrome icon (required for Android 13+) appears correctly when the device theme is dark.
Interacting with the Notification
- Tap – Should launch the
PendingIntentyou defined. Verify the targetActivityis on top of the stack usingadb shell dumpsys activity activities | grep mResumedActivity. - Action buttons – Press each button; check logs for the expected
BroadcastReceiverorIntentServicebeing triggered. - Dismiss – Swipe away; confirm any cleanup logic (e.g., cancelling a pending alarm) runs if you hooked into
onRemove.
Checking Foreground/Background Behavior
| App State | Expected Notification Handling |
|---|---|
| Foreground (visible) | FirebaseMessagingService.onMessageReceived receives data payload; you may choose to show a custom dialog instead of a system notification. |
| Background (not visible) | System shows notification based on payload; tapping behavior defined in setContentIntent. |
| Force‑stopped | No notification should appear (unless using a high‑priority FCM with content_available=true on iOS—irrelevant for Android). |
To test background, press Home or switch to another app before sending the FCM message.
Using Android Studio Logcat
Filter logs for your app’s tag and FCM:
adb logcat | grep -i fcm
Look for:
FirebaseMessagingService: onMessageReceivedentry.NotificationManager: notifycall.- Any exceptions or
ANRwarnings.
Exploratory Checks
- Doze mode – Run
adb shell dumpsys battery unplugto simulate unplugging, thenadb shell dumpsys deviceidle force-idleto trigger Doze. Send a notification and note latency. - Battery optimization exemption – Disable exemption via *Settings → Apps → → Battery → Battery optimization → Not optimized* and observe if delivery slows.
- Notification channel changes – While the app is running, go to system settings and change the importance of a channel; send a new notification and verify the updated behavior (heads‑up vs silent).
---
Automated Approaches
Automation provides repeatability and scalability. Below are techniques ranging from unit tests to cloud‑based device farms, plus a mention of how autonomous, persona‑driven exploration can surface issues that scripted tests miss.
Unit Testing Notification‑Related Logic
Test the construction of NotificationCompat.Builder and any helper methods that decide channel importance or filter payloads.
@Test
public void buildNotification_setsCorrectChannel() {
NotificationCompat.Builder builder = NotificationHelper.createReminderNotification(context);
Notification notif = builder.build();
assertEquals("reminder_channel", notif.getChannelId());
}
Use Robolectric to run Android‑framework tests on the JVM without an emulator or device.
Instrumented Tests with Espresso/UIAutomator
Espresso excels at verifying UI after a notification tap; UIAutomator can interact with the shade and system dialogs.
Example: Espresso test for notification tap
@Rule
public ActivityTestRule<MainActivity> activityRule =
new ActivityTestRule<>(MainActivity.class);
@Test
public void notificationTap_opensDetailScreen() {
// Trigger a local notification via a helper method
NotificationHelper.fireTestNotification(getInstrumentation().getTargetContext());
// Open the shade and click the notification
UiDevice device = UiDevice.getInstance(getInstrumentation());
device.openNotification();
// Wait for the notification to appear
UiObject2 notif = device.wait(Until.findObject(By.text("Test Title")), 5000);
assertNotNull(notif);
notif.click();
// Verify the detail Activity is launched
intended(hasComponent(DetailActivity.class.getName()));
}
UIAutomator for shade interactions
@Test
public void headsUpNotification_isDisplayed() {
UiDevice device = UiDevice.getInstance(getInstrumentation());
// Send a high‑priority notification via adb or FCM from test code
NotificationHelper.sendHighPriorityNotification(getInstrumentation().getTargetContext());
// Wait for heads‑up popup
UiObject2 headsUp = device.wait(Until.findObject(By.clazz("android.widget.TextView")
.text("Test Title")), 5000);
assertNotNull(headsUp);
// Verify it’s visible (not just in shade)
assertTrue(headsUp.isVisible());
}
Run these tests on a matrix of devices via Gradle:
./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.numShards=4
Using Firebase Test Lab
Test Lab lets you execute your instrumentation suite on a wide range of physical and virtual devices in Google’s cloud.
- Upload APK and test APK – Build both with
./gradlew assembleDebug assembleDebugAndroidTest. - Create a test matrix – In the Firebase console, select devices (e.g., Pixel 4 API 33, Samsung S22 API 33, low‑end device API 28).
- Trigger –
gcloud firebase test android run \
--type instrumentation \
--app app-debug.apk \
--test app-debug-test.apk \
--device model=Pixel4,version=33,locale=en,orientation=portrait \
--device model=SamsungGalaxyS22,version=33,locale=en,orientation=portrait
Test Lab automatically collects logs, screenshots, and performance metrics, making it easy to spot device‑specific regressions (e.g., a notification channel that fails on certain OEM skins).
Leveraging SUSA for Autonomous, Persona‑Driven Exploration
SUSA explores the app without pre‑written scripts, simulating distinct user personas (curious, impatient, novice, adversarial, elderly, accessibility‑focused, power user, etc.). When you point SUSA at an APK or a web‑view wrapper, it will:
- Launch the app and systematically interact with UI elements (buttons, switches, text fields).
- Trigger FCM‑based push notifications at random intervals, mimicking real‑world timing.
- Observe how each persona reacts: does an impatient user dismiss a notification before reading? Does an elderly user miss a low‑contrast heads‑up alert? Does an adversarial user attempt to inject malformed payloads via exposed debug endpoints?
Because SUSA builds a session‑level memory of visited screens and dead ends, it learns which notification‑related flows are flaky (e.g., a notification that only appears after a specific sequence of deep links). After a run, SUSA can export Appium (Android) or Playwright (Web) regression scripts that capture the exact steps to reproduce a discovered bug.
Running SUSA locally
pip install susatest-agent
susatest run --apk path/to/app.apk \
--personas curious impatient elderly \
--fcmsimulator \
--output ./susa-report
The --fcmsimulator flag tells SUSA to use its built‑in FCM mock, which can send varied payloads (different channels, action buttons, data‑only messages) while logging the app’s reaction. The resulting report includes:
- Crash stack traces.
- ANR traces with associated UI state.
- WCAG violation screenshots (e.g., low‑contrast notification text).
- Security findings such as PII visible in notification shade.
You can then feed the failing scenarios into your CI pipeline as additional instrumented tests.
---
Edge Cases that Appear Only in Production
Some bugs surface only when the app runs under real‑world constraints: fluctuating networks, aggressive power‑saving policies, or heterogeneous device fleets. Below is a detailed look at those scenarios and how to test for them.
Network Conditions
FCM relies on a persistent HTTPS connection to Google’s servers. If the device switches between Wi‑Fi, cellular, or loses connectivity, the downstream message may be delayed or queued.
Test approach
- Use the Android Emulator’s cellular controls:
adb shell emu network gsm(orlte,evdo,none). - Introduce packet loss with
tcon a rooted device or viaadb shell su -c "tc qdisc add dev wlan0 root netem loss 10%". - Verify that the app eventually receives the notification once connectivity restores, and that no duplicate notifications are generated due to retransmission logic.
Doze Mode and App Standby
Starting with Marshmallow, Doze defers background network access and delays FCM high‑priority messages until a maintenance window. App Standby further restricts apps the user hasn’t interacted with recently.
Test approach
- Force Doze:
adb shell dumpsys deviceidle force-idle. - Send a high‑priority FCM notification and measure latency (
SystemClock.elapsedRealtime()at send vs. receipt). - Exit Doze:
adb shell dumpsys deviceidle unforceand confirm the notification appears promptly. - For App Standby, place the app in the standby bucket:
adb shell cmd appstandby setthenactive adb shell cmd appstandby set. Send a notification and verify it’s still delivered (high‑priority messages bypass standby restrictions).standby
Battery Optimization Whitelist
OEMs often add aggressive battery‑saving layers that can stop background services or alarm managers.
Test approach
- Disable optimization for your app via
adb shell cmd deviceidle whitelist +and re‑enable with-. - Send a notification while the app is in the background and observe if any custom
WorkManagerorAlarmManagerlogic that schedules a reminder fails to fire.
Multiple Notification Channels
Apps targeting API 26+ must create channels; each channel can have its own importance, sound, vibration, and lock‑screen visibility. Misconfiguration leads to notifications being silently dropped or overly intrusive.
Test approach
- Enumerate channels via
adb shell cmd notification listchannels. - For each channel, send a test notification and assert the observed importance matches the channel’s definition (
NotificationManager.getNotificationChannel). - Verify that changing a channel’s importance at runtime (via
setImportance) takes effect for subsequently posted notifications.
Payload Size Limits
FCM imposes a 4 KB limit on the total payload (including keys). Exceeding this results in silent drop.
Test approach
- Construct a JSON payload that increments in size (e.g., add a large custom field).
- Send via FCM and check logcat for
MessagingError: QuotaExceeded. - Ensure your server‑side validation rejects oversized payloads before they reach FCM.
Notification Grouping and Bundling
Starting with Android 9, the system can group notifications by setGroupKey. Misusing group keys can cause unexpected collapsing or expanding behavior.
Test approach
- Send four notifications with the same group key and a distinct summary via
setGroupSummary(true). - Pull down the shade and confirm a single group entry appears.
- Expand the group and verify each child notification is accessible and retains its own data.
- Test edge case: setting a group key on a notification that already belongs to a different group (should replace previous grouping).
Accessibility Considerations
TalkBack users rely on spoken feedback; notification accessibility hinges on proper contentDescription and concise text.
Test approach
- Enable TalkBack (
Settings → Accessibility → TalkBack). - Send a notification and swipe to read it with TalkBack gestures.
- Confirm that the spoken text matches the visual content and that action buttons are announced (e.g., “Reply button”).
- Use the Accessibility Scanner tool to automatically flag low contrast or missing labels.
Security and Privacy
Sensitive data must never appear in the notification text. Additionally, replay attacks can be mitigated by including a nonce or timestamp that the backend validates.
Test approach
- Data leakage – Send a FCM message containing a user’s email in the
datapayload. Ensure your server strips PII before constructing the notificationtitle/text. Verify viaadb logcatthat the notification posted does not contain the email string. - Replay protection – Have your backend embed a monotonically increasing
msgIdin the payload. The app should persist the last seenmsgIdand ignore any message with an equal or lower ID. Test by capturing a valid message, replaying it after a delay, and confirming no UI update occurs. - Token theft simulation – If you expose the FCM token via logs or a debug endpoint, an attacker could reuse it. Ensure your build strips token logging in release (
Log.dstatements removed) and that any endpoint returning the token requires authentication.
---
Integrating Push Notification Tests into CI/CD
Automated tests gain real value when they run on every change, providing fast feedback on regressions. Below is a practical way to embed push‑notification verification into a Git‑centric workflow.
Triggering Tests on Pull Request
Configure your CI system (GitHub Actions, GitLab CI, Bitbucket Pipelines) to run the instrumentation suite on push‑request events.
GitHub Actions example
name: Push Notification Tests
on:
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: '17'
- name: Grant execute permission for Gradlew
run: chmod +x gradlew
- name: Download Firebase Test Lab credentials
run: |
echo "${{ secrets.FIREBASE_SERVICE_ACCOUNT }}" > firebase.json
gcloud auth activate-service-account --key-file firebase.json
- name: Run instrumentation on Test Lab
run: |
./gradlew assembleDebug assembleDebugAndroidTest
gcloud firebase test android run \
--type instrumentation \
--app app/build/outputs/apk/debug/app-debug.apk \
--test app/build/outputs/apk/androidTest/debug/app-debug-test.apk \
--device model=Pixel4,version=33,locale=en,orientation=portrait \
--timeout 2m
This workflow builds the APKs, authenticates with Firebase Test Lab, and executes the test matrix on a Pixel 4 emulator. Adjust the device list to cover your target market.
Publishing Results
Test Lab automatically stores results in a Firebase Storage bucket. You can link those results to the PR via a comment:
gcloud firebase test android result bucket --gsutil \
gs://test-lab-results/<project-id>/<timestamp>/ \
cp results.xml .
Then use the GitHub API to post a comment with a link to the HTML report.
Flaky Test Mitigation
Push‑notification tests can be flaky due to timing variances. Apply these patterns:
- Retry wrapper – In your test framework, wrap flaky assertions in a retry loop with exponential backoff (max 3 attempts).
- Deterministic FCM mock – For unit and instrumentation tests, replace the real FCM downstream with a local mock server (e.g., using
MockWebServer). This removes network jitter. - Time‑budget assertions – Instead of asserting exact delivery time, assert a range (e.g., “notification appears within 5‑30 seconds after sending”).
---
Checklist for Push Notification Testing
Use this concise list before marking a release as ready.
| Area | Item | Verification Method |
|---|---|---|
| Permission | Notification permission granted (runtime & system) | adb shell pm grant |
| Channel | All required channels created with correct importance, sound, vibration | adb shell cmd notification listchannels |
| Foreground | Data‑only messages handled in onMessageReceived without showing a notification (if desired) | Logcat check for handler execution; no shade entry |
| Background | Notification appears with correct title, text, icon, and action intents | Shade inspection + tap test |
| Action Buttons | Each button triggers the intended BroadcastReceiver/Service | Log or UI change after button press |
| Heads‑up | High‑importance notification shows heads‑up popup (if not suppressed) | Visual inspection on unlocked device |
| Lock‑screen | Visibility matches setVisibility (public/private/secret) | Lock device, wake, observe notification |
| Accessibility | TalkBack reads title, text, and actions; contrast ≥ 4.5:1 | TalkBack navigation + Accessibility Scanner |
| Grouping | Notifications with same groupKey collapse; expanding shows all | Send grouped notifications; verify shade behavior |
| Doze / Standby | Notification delivered within expected window after exiting idle states | Force Doze/Standby, measure latency |
| Battery Optimization | Notification still delivered when exemption removed (may be delayed) | Disable exemption, send notification, observe delay |
| Payload Size | Messages > 4 KB are rejected server‑side; no silent drop on device | Send oversized payload, check server logs & device logcat |
| Localization | RTL layout respected; text not truncated | Switch language to Arabic/Hebrew, inspect notification |
| Security | No PII in title/text; replay protection in place | Inspect notification content; resend old payload, ensure ignored |
| Performance | No ANR or excessive CPU when receiving burst of 50 notifications | Stress test with adb shell cmd notification post … in a loop, monitor adb logcat for ANR |
| Fallback Token Refresh | App registers new token with backend after FCM token rotation | Simulate token expiration, verify backend receives updated token |
| CI Integration | Tests run on PR; results visible; failures block merge | Check CI pipeline logs and PR comments |
---
Closing Takeaways
Push notifications are a high‑impact, high‑risk surface. A disciplined testing strategy blends:
- Clear specifications – define which channels, importance levels, and payload shapes your app supports.
- Matrix‑driven coverage – happy path, error paths, edge cases, accessibility, and security must all appear in your test plan.
- Manual exploration – ad‑hoc checks with ADB, Doze forcing, and accessibility tools uncover issues that automated scripts often miss because they depend on device state or timing quirks.
- Automated verification – unit tests for notification builders, Espresso/UIAutomator for post‑tap flows, Firebase Test Lab for device‑farm validation, and, when available, autonomous persona‑driven tools like SUSA to surface rare, production‑only bugs.
- CI gating – run the suite on every pull request, retain reports, and treat any failure as a blocker.
By treating push notifications as a first‑class citizen in your test suite—not an afterthought—you protect user trust, maintain engagement metrics, and avoid costly post‑release incidents. Keep the checklist handy, iterate on your matrix as you add new notification features, and let both scripted and exploratory testing complement each other. Your users will notice the difference in reliability, and your engineering team will spend less time firefighting noisy alerts in production.
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