Las notificaciones push son la columna vertebral de la comunicación en tiempo real en las aplicaciones móviles, pero son notoriamente difíciles de probar debido a su dependencia de infraestructura específica de plataforma (Firebase Cloud Messaging para Android, Apple Push Notification Service para iOS). Según datos de Firebase, las notificaciones push tienen una tasa de entrega promedio del 85-90% cuando se implementan correctamente, sin embargo, las implementaciones mal probadas pueden tener tasas inferiores al 40% por formatos de payload incorrectos y problemas de gestión de tokens. Según un estudio de Airship 2023, las aplicaciones móviles con estrategias de notificaciones push correctamente probadas tienen tasas de engagement de usuario 3-4x más altas. Esta guía cubre estrategias comprensivas para probar integraciones de FCM y APNs, notificaciones locales vs remotas, validación de entrega y testing automatizado.
TL;DR: El testing de notificaciones push requiere validar la entrega de FCM (Android) y APNs (iOS), el formato del payload, la gestión de tokens y casos extremos como la entrega en segundo plano y las notificaciones silenciosas. Usa dispositivos de prueba de Firebase, el entorno sandbox de APNs de Apple y Firebase Test Lab para lograr tasas de entrega superiores al 90% en producción.
Comprendiendo la Arquitectura de Notificaciones Push
Antes de adentrarse en las estrategias de prueba, es esencial comprender cómo funcionan las notificaciones push. La arquitectura involucra varios componentes clave:
Componentes del lado del cliente:
- Aplicación móvil con permisos de notificación
- Token de registro del dispositivo (identificador único)
- Manejadores de notificaciones (primer plano y segundo plano)
Componentes del lado del servidor:
- Servidor backend de la aplicación
- Servicio de notificaciones push (FCM/APNs)
- Cola de mensajes y sistema de entrega
Flujo de notificaciones:
- La app solicita permiso de notificaciones al usuario
- El dispositivo se registra con FCM/APNs y recibe un token
- La app envía el token al servidor backend
- El servidor envía la carga útil de notificación a FCM/APNs
- FCM/APNs enruta la notificación al dispositivo objetivo
- El dispositivo muestra la notificación o activa el manejador de la app
Comprender este flujo ayuda a identificar puntos críticos de prueba a lo largo del ciclo de vida de la notificación.
“Las notificaciones push son una de las características más subestimadas en el testing móvil. La mayoría de los equipos solo prueban el happy path y descubren los fallos de entrega cuando los usuarios empiezan a desinstalar la app.” — Yuri Kan, Senior QA Lead
Pruebas de Firebase Cloud Messaging (FCM)
Firebase Cloud Messaging es la solución de Google para enviar notificaciones a aplicaciones Android, iOS y web. Probar FCM requiere validar tanto la configuración como el comportamiento en tiempo de ejecución.
Validación de Configuración FCM
Antes de probar la entrega de notificaciones, verifica tu configuración FCM:
// Verificar inicialización de FCM en tu app
import messaging from '@react-native-firebase/messaging';
async function checkFCMSetup() {
try {
// Verificar si Firebase está inicializado correctamente
const isSupported = await messaging().isDeviceRegisteredForRemoteMessages();
console.log('Dispositivo registrado para FCM:', isSupported);
// Recuperar y registrar el token FCM
const token = await messaging().getToken();
console.log('Token FCM:', token);
// Verificar token APNs en iOS
if (Platform.OS === 'ios') {
const apnsToken = await messaging().getAPNSToken();
console.log('Token APNs:', apnsToken);
}
return { success: true, token };
} catch (error) {
console.error('Error de configuración FCM:', error);
return { success: false, error };
}
}
Probando Notificaciones FCM
Usa la consola de Firebase o código del lado del servidor para enviar notificaciones de prueba:
// Lado del servidor: Enviar notificación FCM usando Admin SDK
const admin = require('firebase-admin');
async function sendFCMNotification(deviceToken, payload) {
const message = {
notification: {
title: 'Notificación de Prueba',
body: 'Este es un mensaje de prueba',
},
data: {
type: 'test',
timestamp: Date.now().toString(),
action: 'open_screen',
screen: 'home'
},
token: deviceToken,
android: {
priority: 'high',
notification: {
channelId: 'default',
sound: 'default',
color: '#FF0000'
}
},
apns: {
payload: {
aps: {
sound: 'default',
badge: 1,
contentAvailable: true
}
}
}
};
try {
const response = await admin.messaging().send(message);
console.log('Mensaje enviado exitosamente:', response);
return { success: true, messageId: response };
} catch (error) {
console.error('Error al enviar mensaje:', error);
return { success: false, error };
}
}
Lista de Verificación de Pruebas FCM
| Caso de Prueba | Resultado Esperado | Método de Validación |
|---|---|---|
| Generación de token | Token FCM válido recibido | Registrar token, verificar formato |
| Persistencia de token | Token permanece consistente | Verificar token entre reinicios de app |
| Actualización de token | Nuevo token generado al invalidarse | Forzar actualización, verificar cambio |
| Entrega en primer plano | Manejador de app activado | Verificar ejecución del manejador |
| Entrega en segundo plano | Notificación del sistema mostrada | Verificación visual |
| Mensajes solo de datos | Manejador de fondo activado | Verificar procesamiento de datos |
| Mensajes alta prioridad | Entrega inmediata | Medir latencia de entrega |
Pruebas de Apple Push Notification Service (APNs)
APNs es la infraestructura de notificaciones push de Apple para iOS, iPadOS, macOS y watchOS. Probar APNs requiere certificados y perfiles de aprovisionamiento apropiados.
Configuración de APNs
Verifica tu configuración de APNs:
// iOS: Registrar para notificaciones remotas
import UserNotifications
func registerForPushNotifications() {
UNUserNotificationCenter.current().requestAuthorization(
options: [.alert, .sound, .badge]
) { granted, error in
print("Permiso otorgado: \(granted)")
guard granted else { return }
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
// Manejar registro exitoso
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
let token = tokenParts.joined()
print("Token del Dispositivo: \(token)")
// Enviar token al servidor backend
sendTokenToServer(token)
}
// Manejar errores de registro
func application(
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
print("Fallo al registrar: \(error)")
}
Probando APNs con curl
Puedes probar APNs directamente usando HTTP/2:
# Enviar notificación APNs usando curl
curl -v \
--header "apns-topic: com.tuapp.bundle" \
--header "apns-push-type: alert" \
--header "apns-priority: 10" \
--header "authorization: bearer $AUTH_TOKEN" \
--data '{"aps":{"alert":{"title":"Prueba","body":"Mensaje de prueba APNs"},"badge":1,"sound":"default"}}' \
--http2 \
https://api.push.apple.com/3/device/$DEVICE_TOKEN
Pruebas de Certificados APNs
| Tipo de Certificado | Propósito | Enfoque de Prueba |
|---|---|---|
| Desarrollo | Pruebas en builds de desarrollo | Usar endpoint sandbox de APNs |
| Producción | Notificaciones de app en producción | Usar endpoint de producción de APNs |
| VoIP | Notificaciones push VoIP | Probar con framework PushKit |
| Certificados expirados | Manejo de errores | Verificar mensajes de error |
Notificaciones Locales vs Remotas
Comprender la diferencia entre notificaciones locales y remotas es crucial para pruebas exhaustivas.
Notificaciones Locales
Las notificaciones locales son programadas por la app misma:
// React Native: Programar notificación local
import PushNotification from 'react-native-push-notification';
function scheduleLocalNotification() {
PushNotification.localNotificationSchedule({
title: 'Notificación Programada',
message: 'Esta notificación fue programada localmente',
date: new Date(Date.now() + 60 * 1000), // 1 minuto desde ahora
playSound: true,
soundName: 'default',
actions: ['Ver', 'Descartar'],
userInfo: {
type: 'local',
action: 'open_detail'
}
});
}
// Probar notificación local inmediata
function showLocalNotification() {
PushNotification.localNotification({
title: 'Notificación Inmediata',
message: 'Esta notificación aparece inmediatamente',
bigText: 'Este es el texto más largo que se mostrará cuando se expanda la notificación',
largeIcon: 'ic_launcher',
smallIcon: 'ic_notification'
});
}
Notificaciones Remotas
Las notificaciones remotas son enviadas desde un servidor:
// iOS: Manejar notificación remota
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
let userInfo = response.notification.request.content.userInfo
// Extraer datos personalizados
if let type = userInfo["type"] as? String,
let action = userInfo["action"] as? String {
handleNotificationAction(type: type, action: action)
}
completionHandler()
}
Tabla Comparativa
| Característica | Notificaciones Locales | Notificaciones Remotas |
|---|---|---|
| Activador | Programadas por la app | Enviadas por servidor |
| Red requerida | No | Sí |
| Garantía de entrega | Alta (basada en dispositivo) | Depende de la red |
| Contenido dinámico | Limitado | Totalmente personalizable |
| Segmentación de usuarios | Solo dispositivo | Lógica del lado del servidor |
| Complejidad de prueba | Baja | Media a alta |
Estructura y Validación de Carga Útil de Notificación
La estructura adecuada de la carga útil es crítica para la entrega y manejo de notificaciones.
Estructura de Carga Útil FCM
{
"message": {
"token": "token_fcm_dispositivo",
"notification": {
"title": "Noticias de Última Hora",
"body": "Nuevo artículo publicado",
"image": "https://ejemplo.com/imagen.jpg"
},
"data": {
"article_id": "12345",
"category": "tecnologia",
"priority": "high",
"click_action": "ABRIR_ARTICULO"
},
"android": {
"priority": "high",
"ttl": "3600s",
"notification": {
"channel_id": "actualizaciones_noticias",
"color": "#FF5722",
"sound": "sonido_notificacion",
"tag": "noticias",
"click_action": "ABRIR_ARTICULO"
}
},
"apns": {
"headers": {
"apns-priority": "10",
"apns-expiration": "1609459200"
},
"payload": {
"aps": {
"alert": {
"title": "Noticias de Última Hora",
"body": "Nuevo artículo publicado",
"launch-image": "imagen_notificacion"
},
"badge": 5,
"sound": "notification.caf",
"category": "CATEGORIA_NOTICIAS",
"thread-id": "hilo-noticias"
},
"article_id": "12345"
}
}
}
}
Estructura de Carga Útil APNs
{
"aps": {
"alert": {
"title": "Pago Recibido",
"subtitle": "$500.00 depositados",
"body": "Tu pago ha sido procesado exitosamente"
},
"badge": 1,
"sound": "pago_exitoso.caf",
"category": "CATEGORIA_PAGO",
"thread-id": "pago-12345",
"content-available": 1,
"mutable-content": 1
},
"transaction_id": "TXN-67890",
"amount": "500.00",
"timestamp": "2025-10-04T10:30:00Z"
}
Pruebas de Validación de Carga Útil
// Validar carga útil de notificación
function validateNotificationPayload(payload) {
const errors = [];
// Verificar campos requeridos
if (!payload.notification?.title) {
errors.push('Falta título de notificación');
}
if (!payload.notification?.body) {
errors.push('Falta cuerpo de notificación');
}
// Validar longitud del título (límite FCM: 65 caracteres)
if (payload.notification?.title?.length > 65) {
errors.push('Título excede 65 caracteres');
}
// Validar longitud del cuerpo (límite FCM: 240 caracteres)
if (payload.notification?.body?.length > 240) {
errors.push('Cuerpo excede 240 caracteres');
}
// Validar tamaño de carga útil de datos (límite 4KB)
const dataSize = JSON.stringify(payload.data || {}).length;
if (dataSize > 4096) {
errors.push('Carga útil de datos excede límite de 4KB');
}
// Verificar claves reservadas
const reservedKeys = ['from', 'notification', 'message_type'];
if (payload.data) {
reservedKeys.forEach(key => {
if (key in payload.data) {
errors.push(`Clave reservada '${key}' usada en carga útil de datos`);
}
});
}
return {
valid: errors.length === 0,
errors
};
}
Probando Entrega y Recepción de Notificaciones
Las pruebas de entrega aseguran que las notificaciones lleguen a los usuarios de manera confiable.
Métodos de Verificación de Entrega
// Lado del cliente: Rastrear recepción de notificación
import messaging from '@react-native-firebase/messaging';
import analytics from '@react-native-firebase/analytics';
messaging().onMessage(async remoteMessage => {
console.log('Notificación recibida (primer plano):', remoteMessage);
// Registrar recepción para analytics
await analytics().logEvent('notification_received', {
notification_id: remoteMessage.data?.notification_id,
type: remoteMessage.data?.type,
timestamp: Date.now()
});
// Enviar confirmación de entrega al servidor
await confirmNotificationDelivery(remoteMessage.data?.notification_id);
});
messaging().setBackgroundMessageHandler(async remoteMessage => {
console.log('Notificación recibida (segundo plano):', remoteMessage);
await analytics().logEvent('notification_received_background', {
notification_id: remoteMessage.data?.notification_id
});
});
Rastreo de Entrega del Lado del Servidor
// Rastrear estado de entrega de notificación
async function trackNotificationDelivery(notificationId) {
const db = admin.firestore();
try {
await db.collection('notifications').doc(notificationId).update({
delivery_status: 'delivered',
delivered_at: admin.firestore.FieldValue.serverTimestamp(),
delivery_latency_ms: Date.now() - sentTimestamp
});
} catch (error) {
console.error('Fallo al rastrear entrega:', error);
}
}
Escenarios de Prueba de Entrega
| Escenario | Método de Prueba | Criterio de Éxito |
|---|---|---|
| Entrega inmediata | Enviar notificación, medir latencia | Recibida en 5 segundos |
| Red sin conexión | Deshabilitar red, enviar notificación | Entregada al conectarse |
| App detenida forzosamente | Detener app, enviar notificación | Notificación aparece |
| Modo batería baja | Habilitar ahorro de batería, enviar | Entrega puede retrasarse |
| Múltiples notificaciones | Enviar ráfaga de 10 notificaciones | Todas recibidas en orden |
| Notificación expirada | Establecer TTL corto, retrasar entrega | Notificación no entregada |
Probando Acciones de Notificación y Deep Linking
Las notificaciones a menudo incluyen acciones que navegan a los usuarios a pantallas específicas de la app.
Implementando Acciones de Notificación
// Definir categorías de notificación con acciones
import PushNotificationIOS from '@react-native-community/push-notification-ios';
PushNotificationIOS.setNotificationCategories([
{
id: 'CATEGORIA_MENSAJE',
actions: [
{ id: 'reply', title: 'Responder', options: { foreground: true } },
{ id: 'mark_read', title: 'Marcar como Leído', options: { foreground: false } },
{ id: 'delete', title: 'Eliminar', options: { destructive: true } }
]
},
{
id: 'CATEGORIA_EVENTO',
actions: [
{ id: 'accept', title: 'Aceptar', options: { foreground: true } },
{ id: 'decline', title: 'Rechazar', options: { foreground: false } }
]
}
]);
// Manejar acciones de notificación
PushNotificationIOS.addEventListener('notificationAction', (notification) => {
const action = notification.action;
const userInfo = notification.userInfo;
switch(action) {
case 'reply':
navigateToChat(userInfo.chat_id);
break;
case 'mark_read':
markMessageAsRead(userInfo.message_id);
break;
case 'delete':
deleteMessage(userInfo.message_id);
break;
}
notification.finish(PushNotificationIOS.FetchResult.NoData);
});
Pruebas de Deep Linking
// Manejar deep links desde notificaciones
import { Linking } from 'react-native';
async function handleNotificationDeepLink(notification) {
const deepLink = notification.data?.deep_link;
if (!deepLink) return;
try {
// Validar formato de deep link
const url = new URL(deepLink);
// Parsear parámetros de deep link
const route = url.pathname;
const params = Object.fromEntries(url.searchParams);
// Navegar a pantalla apropiada
switch(route) {
case '/article':
navigation.navigate('Article', { id: params.article_id });
break;
case '/profile':
navigation.navigate('Profile', { userId: params.user_id });
break;
case '/settings':
navigation.navigate('Settings', { tab: params.tab });
break;
default:
navigation.navigate('Home');
}
} catch (error) {
console.error('Deep link inválido:', error);
}
}
Matriz de Prueba de Acciones
| Tipo de Acción | Caso de Prueba | Validación |
|---|---|---|
| Acción de primer plano | Tocar “Responder” | App abre en pantalla de respuesta |
| Acción de segundo plano | Tocar “Marcar Leído” | Acción completa, sin lanzar app |
| Acción destructiva | Tocar “Eliminar” | Confirmación mostrada, acción completa |
| Acción entrada de texto | Responder con texto | Texto enviado al manejador |
| Deep link | Tocar notificación | Pantalla correcta abierta con parámetros |
| Deep link inválido | URL mal formada | Retroceso elegante al inicio |
Probando Permisos de Notificación
El manejo de permisos es crítico para la funcionalidad de notificaciones.
Prueba de Solicitud de Permisos
// Solicitar permisos de notificación
import { PermissionsAndroid, Platform } from 'react-native';
import messaging from '@react-native-firebase/messaging';
async function requestNotificationPermission() {
try {
if (Platform.OS === 'android' && Platform.Version >= 33) {
// Android 13+ requiere permiso en tiempo de ejecución
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
);
return granted === PermissionsAndroid.RESULTS.GRANTED;
} else if (Platform.OS === 'ios') {
// Solicitud de permiso iOS
const authStatus = await messaging().requestPermission({
alert: true,
badge: true,
sound: true,
provisional: false
});
return authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
}
return true; // Android < 13 no requiere permiso en tiempo de ejecución
} catch (error) {
console.error('Fallo solicitud de permiso:', error);
return false;
}
}
// Verificar estado actual de permiso
async function checkNotificationPermission() {
const authStatus = await messaging().getNotificationSettings();
return {
enabled: authStatus.authorizationStatus === messaging.AuthorizationStatus.AUTHORIZED,
provisional: authStatus.authorizationStatus === messaging.AuthorizationStatus.PROVISIONAL,
denied: authStatus.authorizationStatus === messaging.AuthorizationStatus.DENIED,
alert: authStatus.alert === 1,
badge: authStatus.badge === 1,
sound: authStatus.sound === 1
};
}
Prueba de Estado de Permisos
| Estado de Permiso | Escenario de Prueba | Comportamiento Esperado |
|---|---|---|
| No determinado | Primer lanzamiento de app | Diálogo de permiso mostrado |
| Otorgado | Permiso permitido | Notificaciones entregadas |
| Denegado | Permiso rechazado | Sin notificaciones, degradación elegante |
| Provisional (iOS) | Permiso silencioso | Notificaciones solo en centro de notificaciones |
| Revocado | Permiso deshabilitado en ajustes | Solicitud para reactivar |
| Limitado (iOS 15+) | Notificaciones sensibles al tiempo | Solo notificaciones críticas |
Manejo de Notificaciones en Segundo Plano y Primer Plano
Las notificaciones se comportan de manera diferente según el estado de la app.
Manejo en Primer Plano
// Manejar notificaciones cuando app está en primer plano
messaging().onMessage(async remoteMessage => {
console.log('Notificación en primer plano:', remoteMessage);
// Mostrar notificación personalizada dentro de la app
showInAppNotification({
title: remoteMessage.notification.title,
body: remoteMessage.notification.body,
onPress: () => handleNotificationPress(remoteMessage.data)
});
// Actualizar contador de insignia
if (Platform.OS === 'ios') {
PushNotificationIOS.setApplicationIconBadgeNumber(
remoteMessage.data?.badge_count || 1
);
}
});
Manejo en Segundo Plano
// Manejar notificaciones cuando app está en segundo plano
messaging().setBackgroundMessageHandler(async remoteMessage => {
console.log('Notificación en segundo plano:', remoteMessage);
// Procesar mensajes solo de datos
if (!remoteMessage.notification && remoteMessage.data) {
await processBackgroundData(remoteMessage.data);
}
// Actualizar base de datos local
await updateLocalCache(remoteMessage.data);
return Promise.resolve();
});
// Manejar apertura de notificación desde segundo plano/estado detenido
messaging().onNotificationOpenedApp(remoteMessage => {
console.log('Notificación abierta (segundo plano):', remoteMessage);
handleNotificationNavigation(remoteMessage.data);
});
// Verificar si notificación abrió app desde estado detenido
messaging().getInitialNotification().then(remoteMessage => {
if (remoteMessage) {
console.log('Notificación abierta (estado detenido):', remoteMessage);
handleNotificationNavigation(remoteMessage.data);
}
});
Notificaciones Silenciosas y Actualizaciones en Segundo Plano
Las notificaciones silenciosas permiten sincronización de datos en segundo plano.
Implementando Notificaciones Silenciosas
// iOS: Habilitar modos de fondo en Info.plist
// <key>UIBackgroundModes</key>
// <array>
// <string>remote-notification</string>
// <string>fetch</string>
// </array>
// Manejar notificación silenciosa
messaging().setBackgroundMessageHandler(async remoteMessage => {
if (remoteMessage.data?.silent === 'true') {
// Realizar sincronización en segundo plano
await syncDataInBackground();
// Descargar contenido
if (remoteMessage.data?.content_url) {
await downloadContent(remoteMessage.data.content_url);
}
// Actualizar insignia de app
if (remoteMessage.data?.badge) {
await updateBadgeCount(parseInt(remoteMessage.data.badge));
}
}
});
Carga Útil de Notificación Silenciosa
{
"message": {
"token": "device_token",
"data": {
"silent": "true",
"sync_type": "messages",
"badge": "5"
},
"apns": {
"headers": {
"apns-priority": "5",
"apns-push-type": "background"
},
"payload": {
"aps": {
"content-available": 1
}
}
},
"android": {
"priority": "normal"
}
}
}
Probando Agrupación y Categorías de Notificaciones
La agrupación de notificaciones mejora la experiencia del usuario organizando notificaciones relacionadas.
Canales de Notificación Android
// Crear canales de notificación (Android 8.0+)
import PushNotification from 'react-native-push-notification';
PushNotification.createChannel(
{
channelId: 'messages',
channelName: 'Mensajes',
channelDescription: 'Notificaciones para mensajes nuevos',
playSound: true,
soundName: 'message_sound.mp3',
importance: 4, // Importancia alta
vibrate: true
},
created => console.log(`Canal creado: ${created}`)
);
PushNotification.createChannel({
channelId: 'promotions',
channelName: 'Promociones',
channelDescription: 'Notificaciones promocionales',
playSound: false,
importance: 2, // Importancia baja
vibrate: false
});
Grupos de Notificación iOS
// Crear contenido de notificación con identificador de hilo
let content = UNMutableNotificationContent()
content.title = "Nuevo Mensaje"
content.body = "Tienes un nuevo mensaje de Juan"
content.threadIdentifier = "mensaje-hilo-123"
content.summaryArgument = "Juan"
content.summaryArgumentCount = 1
Manejo de Errores y Casos Extremos
El manejo robusto de errores asegura que las notificaciones funcionen de manera confiable.
Escenarios Comunes de Error
// Manejo integral de errores
async function sendNotificationWithErrorHandling(deviceToken, payload) {
try {
const response = await admin.messaging().send({
token: deviceToken,
...payload
});
return { success: true, messageId: response };
} catch (error) {
console.error('Error de notificación:', error);
// Manejar códigos de error específicos
switch(error.code) {
case 'messaging/invalid-registration-token':
case 'messaging/registration-token-not-registered':
// Token inválido, eliminar de base de datos
await removeInvalidToken(deviceToken);
return { success: false, error: 'Token inválido', action: 'removed' };
case 'messaging/message-rate-exceeded':
// Demasiados mensajes, implementar retroceso
return { success: false, error: 'Límite de tasa', action: 'retry' };
case 'messaging/mismatched-credential':
// Credenciales FCM incorrectas
return { success: false, error: 'Error de autenticación', action: 'check_config' };
case 'messaging/invalid-payload':
// Carga útil mal formada
return { success: false, error: 'Carga útil inválida', action: 'validate' };
default:
return { success: false, error: error.message, action: 'log' };
}
}
}
Prueba de Casos Extremos
| Caso Extremo | Enfoque de Prueba | Manejo Esperado |
|---|---|---|
| Token expirado | Enviar a token antiguo | Error detectado, token actualizado |
| App desinstalada | Enviar a app desinstalada | Token marcado como inactivo |
| Timeout de red | Simular red lenta | Reintentar con retroceso exponencial |
| Carga útil muy grande | Enviar carga >4KB | Error retornado, carga rechazada |
| Caracteres inválidos | Usar emoji/caracteres especiales | Codificado y mostrado correctamente |
| Notificaciones concurrentes | Enviar 100+ notificaciones | Todas entregadas, agrupadas adecuadamente |
| Manejo de zona horaria | Programar a través de zonas | Entrega en hora local correcta |
Estrategias de Prueba Automatizada de Notificaciones
La automatización mejora la eficiencia y cobertura de pruebas.
Marco de Pruebas de Integración
// Pruebas automatizadas de notificación con Jest
describe('Pruebas de Notificaciones Push', () => {
let testDeviceToken;
beforeAll(async () => {
// Configurar entorno de prueba
testDeviceToken = await getTestDeviceToken();
});
test('debería enviar y recibir notificación', async () => {
const notificationId = `test-${Date.now()}`;
// Enviar notificación
const result = await sendTestNotification(testDeviceToken, {
title: 'Notificación de Prueba',
body: 'Prueba automatizada',
data: { notification_id: notificationId }
});
expect(result.success).toBe(true);
// Esperar entrega
await new Promise(resolve => setTimeout(resolve, 3000));
// Verificar recepción
const delivered = await checkNotificationDelivered(notificationId);
expect(delivered).toBe(true);
});
test('debería manejar token inválido elegantemente', async () => {
const invalidToken = 'token-invalido-12345';
const result = await sendTestNotification(invalidToken, {
title: 'Prueba',
body: 'Debería fallar'
});
expect(result.success).toBe(false);
expect(result.error).toContain('Token inválido');
});
test('debería validar estructura de carga útil', () => {
const validPayload = {
notification: { title: 'Prueba', body: 'Mensaje' },
data: { key: 'value' }
};
const validation = validateNotificationPayload(validPayload);
expect(validation.valid).toBe(true);
expect(validation.errors).toHaveLength(0);
});
test('debería rechazar carga útil de gran tamaño', () => {
const largeData = 'x'.repeat(5000);
const invalidPayload = {
notification: { title: 'Prueba', body: 'Mensaje' },
data: { large_field: largeData }
};
const validation = validateNotificationPayload(invalidPayload);
expect(validation.valid).toBe(false);
expect(validation.errors).toContain('Carga útil de datos excede límite de 4KB');
});
});
Pruebas End-to-End
// Pruebas E2E de notificación con Detox
describe('Pruebas E2E de Notificaciones', () => {
beforeAll(async () => {
await device.launchApp({
permissions: { notifications: 'YES' }
});
});
it('debería solicitar permiso de notificación', async () => {
await element(by.id('enable-notifications-btn')).tap();
await expect(element(by.text('Permiso Otorgado'))).toBeVisible();
});
it('debería mostrar notificación en primer plano', async () => {
// Enviar notificación de prueba
await sendE2ENotification({
title: 'Prueba E2E',
body: 'Probando visualización de notificación'
});
// Verificar que aparece notificación dentro de la app
await waitFor(element(by.text('Prueba E2E')))
.toBeVisible()
.withTimeout(5000);
});
it('debería navegar al tocar notificación', async () => {
await sendE2ENotification({
title: 'Prueba de Navegación',
data: { deep_link: 'miapp://articulo/123' }
});
await device.sendToHome();
await device.launchApp({ newInstance: false });
// Tocar notificación en bandeja del sistema
await device.openNotification();
// Verificar que se abrió pantalla correcta
await expect(element(by.id('article-screen'))).toBeVisible();
});
});
Conclusión
Probar notificaciones push requiere un enfoque integral que cubra la configuración de infraestructura, verificación de entrega, interacción del usuario y manejo de casos extremos. Siguiendo las estrategias descritas en esta guía, puedes asegurar que tus notificaciones push proporcionen una experiencia de usuario confiable y atractiva.
Las prioridades clave de prueba incluyen verificar la configuración de FCM/APNs, validar la estructura de carga útil, probar a través de diferentes estados de app, manejar permisos apropiadamente e implementar manejo robusto de errores. Los marcos de prueba automatizada ayudan a mantener la calidad de notificaciones a medida que tu aplicación evoluciona.
Recuerda que las notificaciones push impactan directamente el compromiso y retención de usuarios, haciendo que las pruebas exhaustivas sean esenciales para el éxito de aplicaciones móviles.
Recursos Oficiales
FAQ
¿Cómo pruebo notificaciones push sin un dispositivo real?
Usa Firebase Test Lab para Android y Xcode Simulator con sandbox de APNs para iOS. El emulador de Firebase soporta testing de FCM localmente. Herramientas como Proxyman y Charles Proxy pueden interceptar solicitudes para verificar el formato del payload.
¿Qué causa los fallos de entrega de notificaciones push?
Causas comunes: tokens de dispositivo expirados o inválidos, formato de payload incorrecto, entorno incorrecto (producción vs sandbox), restricciones de apps en background en iOS y errores de registration ID de FCM.
¿Cómo pruebo notificaciones push silenciosas?
Las notificaciones silenciosas requieren testing en modo background. En iOS: activa Background Modes, prueba con la app en segundo plano. En Android: prueba el comportamiento en modo doze.
¿Qué debo probar para el deep linking en notificaciones push?
El esquema URL correcto abre la pantalla correcta, el deep link funciona con la app cerrada (inicio en frío), manejo gracioso de deep links inválidos y comportamiento correcto del back stack.
See Also
- Testing en Jetpack Compose: Guía Completa para Pruebas de UI en Android Moderno - Guía completa de testing en Jetpack Compose: semantics, test…
- Flutter Testing: Guía Completa de Tests Unitarios, de Widgets e Integración - Domina el testing en Flutter: tests unitarios, de widgets e…
- Testing de GraphQL: Guía completa con ejemplos - Testing completo de GraphQL: pruebas de queries, mutaciones,…
- Testing de gRPC: Guía completa para testing de API RPC - Pruebas de APIs gRPC: protocol buffers, tipos de streaming,…
