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:

  1. La app solicita permiso de notificaciones al usuario
  2. El dispositivo se registra con FCM/APNs y recibe un token
  3. La app envía el token al servidor backend
  4. El servidor envía la carga útil de notificación a FCM/APNs
  5. FCM/APNs enruta la notificación al dispositivo objetivo
  6. 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 PruebaResultado EsperadoMétodo de Validación
Generación de tokenToken FCM válido recibidoRegistrar token, verificar formato
Persistencia de tokenToken permanece consistenteVerificar token entre reinicios de app
Actualización de tokenNuevo token generado al invalidarseForzar actualización, verificar cambio
Entrega en primer planoManejador de app activadoVerificar ejecución del manejador
Entrega en segundo planoNotificación del sistema mostradaVerificación visual
Mensajes solo de datosManejador de fondo activadoVerificar procesamiento de datos
Mensajes alta prioridadEntrega inmediataMedir 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 CertificadoPropósitoEnfoque de Prueba
DesarrolloPruebas en builds de desarrolloUsar endpoint sandbox de APNs
ProducciónNotificaciones de app en producciónUsar endpoint de producción de APNs
VoIPNotificaciones push VoIPProbar con framework PushKit
Certificados expiradosManejo de erroresVerificar 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ísticaNotificaciones LocalesNotificaciones Remotas
ActivadorProgramadas por la appEnviadas por servidor
Red requeridaNo
Garantía de entregaAlta (basada en dispositivo)Depende de la red
Contenido dinámicoLimitadoTotalmente personalizable
Segmentación de usuariosSolo dispositivoLógica del lado del servidor
Complejidad de pruebaBajaMedia 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

EscenarioMétodo de PruebaCriterio de Éxito
Entrega inmediataEnviar notificación, medir latenciaRecibida en 5 segundos
Red sin conexiónDeshabilitar red, enviar notificaciónEntregada al conectarse
App detenida forzosamenteDetener app, enviar notificaciónNotificación aparece
Modo batería bajaHabilitar ahorro de batería, enviarEntrega puede retrasarse
Múltiples notificacionesEnviar ráfaga de 10 notificacionesTodas recibidas en orden
Notificación expiradaEstablecer TTL corto, retrasar entregaNotificació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ónCaso de PruebaValidación
Acción de primer planoTocar “Responder”App abre en pantalla de respuesta
Acción de segundo planoTocar “Marcar Leído”Acción completa, sin lanzar app
Acción destructivaTocar “Eliminar”Confirmación mostrada, acción completa
Acción entrada de textoResponder con textoTexto enviado al manejador
Deep linkTocar notificaciónPantalla correcta abierta con parámetros
Deep link inválidoURL mal formadaRetroceso 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 PermisoEscenario de PruebaComportamiento Esperado
No determinadoPrimer lanzamiento de appDiálogo de permiso mostrado
OtorgadoPermiso permitidoNotificaciones entregadas
DenegadoPermiso rechazadoSin notificaciones, degradación elegante
Provisional (iOS)Permiso silenciosoNotificaciones solo en centro de notificaciones
RevocadoPermiso deshabilitado en ajustesSolicitud para reactivar
Limitado (iOS 15+)Notificaciones sensibles al tiempoSolo 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 ExtremoEnfoque de PruebaManejo Esperado
Token expiradoEnviar a token antiguoError detectado, token actualizado
App desinstaladaEnviar a app desinstaladaToken marcado como inactivo
Timeout de redSimular red lentaReintentar con retroceso exponencial
Carga útil muy grandeEnviar carga >4KBError retornado, carga rechazada
Caracteres inválidosUsar emoji/caracteres especialesCodificado y mostrado correctamente
Notificaciones concurrentesEnviar 100+ notificacionesTodas entregadas, agrupadas adecuadamente
Manejo de zona horariaProgramar a través de zonasEntrega 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.