- Published on
- 5 min read
> Testing Push Notifications on iOS Simulators with xcrun simctl push
Testing push notifications has historically been one of the more tedious parts of iOS development. You needed a physical device, a valid APNs certificate or key, a server to send the payload, and a lot of patience. Starting with Xcode 11.4, Apple introduced a much simpler approach: xcrun simctl push, which lets you send push notifications directly to the iOS Simulator from the command line.
The Basics
The simctl push command sends a simulated push notification to an app running on the simulator. At its simplest, you need a JSON payload file and either the app's bundle identifier or a booted simulator.
Create a file called notification.apns with your push payload:
{
"aps": {
"alert": {
"title": "New Message",
"body": "You have a new message from Sarah"
},
"badge": 3,
"sound": "default"
}
}
Then send it to your app:
xcrun simctl push booted com.yourcompany.yourapp notification.apns
The booted keyword targets whichever simulator is currently running. If you have multiple simulators running, you can specify a device UUID instead:
xcrun simctl list devices | grep Booted
# Find the UUID, then:
xcrun simctl push 8A5C2F1D-3E4B-4A6C-9D8E-1F2A3B4C5D6E com.yourcompany.yourapp notification.apns
Embedding the Bundle Identifier
Typing the bundle identifier every time gets old quickly. You can embed it directly in the payload file using the Simulator Target Bundle key:
{
"Simulator Target Bundle": "com.yourcompany.yourapp",
"aps": {
"alert": {
"title": "Order Shipped",
"body": "Your order #12345 has shipped!"
},
"sound": "default"
}
}
Now the command simplifies to:
xcrun simctl push booted notification.apns
Rich Notification Payloads
You can test more complex notification types including rich media, categories, and custom data. Here's a payload that includes an action category and custom data:
{
"Simulator Target Bundle": "com.yourcompany.yourapp",
"aps": {
"alert": {
"title": "Flash Sale!",
"subtitle": "Limited time offer",
"body": "50% off all items for the next 2 hours"
},
"badge": 1,
"sound": "default",
"category": "SALE_CATEGORY",
"mutable-content": 1
},
"saleId": "summer-flash-2026",
"discount": 50
}
The mutable-content flag tells iOS to invoke your Notification Service Extension if you have one, allowing you to modify the notification before display.
Testing Background Notifications
Silent push notifications (used for background updates) work too. Just include the content-available key and omit the alert:
{
"Simulator Target Bundle": "com.yourcompany.yourapp",
"aps": {
"content-available": 1
},
"updateType": "sync",
"timestamp": 1709424000
}
Your app needs the Background Modes capability with "Remote notifications" enabled for this to trigger your app delegate's application(_:didReceiveRemoteNotification:fetchCompletionHandler:) method.
Setting Up Your App
For notifications to appear, your app needs to request permission. Here's a minimal setup:
import SwiftUI
import UserNotifications
@main
struct NotificationTestApp: App {
init() {
requestNotificationPermission()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
private func requestNotificationPermission() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
if granted {
print("Notification permission granted")
}
}
}
}
When testing, make sure to:
- Launch your app at least once to trigger the permission request
- Accept the notification permission dialog
- Background the app (notifications won't show as banners while your app is in the foreground unless you implement
UNUserNotificationCenterDelegate)
Handling Foreground Notifications
By default, iOS doesn't display notification banners when your app is in the foreground. If you want to see banners while testing in the app, implement UNUserNotificationCenterDelegate:
import SwiftUI
import UserNotifications
@main
struct NotificationTestApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { _, _ in }
return true
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
return [.banner, .sound, .badge]
}
}
Creating a Test Script
For frequent testing, create a shell script with multiple notification types:
#!/bin/bash
# Push a simple alert
cat << 'EOF' | xcrun simctl push booted -
{
"Simulator Target Bundle": "com.yourcompany.yourapp",
"aps": {
"alert": "Quick test notification"
}
}
EOF
The - argument tells simctl to read the payload from stdin, which is convenient for quick one-liners without needing a separate file.
Drag and Drop Alternative
If you prefer a visual approach, you can drag an .apns file directly onto the Simulator window. Make sure the file includes the Simulator Target Bundle key so iOS knows which app should receive it.
Limitations
While simctl push is great for development, keep in mind that it doesn't test the actual APNs infrastructure. Your production server, certificates, and network connectivity aren't involved. Before shipping, you should still test end-to-end with real push notifications on a physical device.
The simulator also doesn't support all notification features identically. Interactive notifications and notification extensions work, but behaviors around delivery timing and grouping may differ slightly from real devices.
For comprehensive testing strategies and device configuration, check out Apple's documentation on Generating a remote notification and the simctl man page.
Sample Project
Want to see this code in action? Check out the complete sample project on GitHub:
The repository includes a working Xcode project with all the examples from this article, plus unit tests you can run to verify the behavior.
// Continue_Learning
Simulating Device Conditions in Xcode for Real-World Testing
Learn how to use Xcode's Device Conditions feature to simulate thermal state and network conditions to test your app under real-world stress scenarios.
Fixing iOS Simulator That Won't Launch or Boot
When the iOS Simulator refuses to launch, gets stuck booting, or shows a black screen, here's how to diagnose and fix it.
Fixing CocoaPods "Unknown ISA PBXFileSystemSynchronizedRootGroup" Error
How to resolve the pod install error caused by Xcode 16's new folder-based group structure.
// Stay Updated
Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.