Las notificaciones push son un componente crítico de las aplicaciones móviles, permitiendo la comunicación en tiempo real con los usuarios (como se discute en Mobile Testing in 2025: iOS, Android and Beyond) incluso cuando la aplicación no está activamente en ejecución. Probar las notificaciones push requiere comprender tanto la infraestructura técnica (Firebase Cloud Messaging para Android (como se discute en Cross-Platform Mobile Testing: Strategies for Multi-Device Success) (como se discute en Appium 2.0: New Architecture and Cloud Integration for Modern Mobile Testing), Apple Push Notification Service para iOS) como las implicaciones en la experiencia del usuario. Esta guía integral cubre todo lo que necesitas saber sobre cómo probar las notificaciones push de manera efectiva.
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.
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.