Las aplicaciones móviles deben funcionar confiablemente en varias condiciones de red. Probar conexiones lentas, conectividad intermitente y escenarios offline asegura experiencias de usuario robustas. La confiabilidad de red impacta directamente el rendimiento de aplicaciones móviles, haciendo la simulación de condiciones esencial para QA.

Android Simulación de Red

Usando Interceptores OkHttp

class NetworkConditionInterceptor(
    private val delayMs: Long = 0,
    private val packetLossRate: Double = 0.0,
    private val bandwidthBytesPerSecond: Long = Long.MAX_VALUE
) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        // Simular latencia
        if (delayMs > 0) {
            Thread.sleep(delayMs)
        }

        // Simular pérdida de paquetes
        if (Random.nextDouble() < packetLossRate) {
            throw SocketTimeoutException("Pérdida de paquetes simulada")
        }

        return chain.proceed(chain.request())
    }
}

// Uso
val client = OkHttpClient.Builder()
    .addInterceptor(
        NetworkConditionInterceptor(
            delayMs = 500,
            packetLossRate = 0.1,
            bandwidthBytesPerSecond = 128_000
        )
    )
    .build()

Testing Diferentes Condiciones

@Test
fun testSlowConnection() = runTest {
    val slowClient = OkHttpClient.Builder()
        .addInterceptor(NetworkConditionInterceptor(
            delayMs = 2000,
            bandwidthBytesPerSecond = 50_000
        ))
        .build()

    val startTime = System.currentTimeMillis()
    val response = apiService.getUsers()
    val duration = System.currentTimeMillis() - startTime

    assertTrue(duration >= 2000)
}

@Test
fun testPacketLoss() = runTest {
    val unreliableClient = OkHttpClient.Builder()
        .addInterceptor(NetworkConditionInterceptor(
            packetLossRate = 0.5
        ))
        .build()

    var failureCount = 0

    repeat(100) {
        try {
            apiService.getUsers()
        } catch (e: SocketTimeoutException) {
            failureCount++
        }
    }

    assertTrue(failureCount in 40..60)
}

Testing Modo Offline

El testing de condiciones de red es crucial para testing móvil en 2025, donde apps iOS y Android enfrentan diversos escenarios de conectividad.

@Test
fun testOfflineDataAccess() = runTest {
    val offlineClient = OkHttpClient.Builder()
        .addInterceptor { chain ->
            throw UnknownHostException("Sin red disponible")
        }
        .build()

    val repository = UserRepository(offlineClient)
    val users = repository.getUsers()

    assertNotNull(users)
    verify { localDatabase.getUsers() }
}

@Test
fun testOfflineWriteQueueing() = runTest {
    syncManager.setNetworkAvailable(false)

    val newUser = User(id = "123", name = "Usuario Test")
    syncManager.queueCreateUser(newUser)

    val pending = syncManager.getPendingOperations()
    assertEquals(1, pending.size)

    syncManager.setNetworkAvailable(true)
    delay(1000)

    assertEquals(0, syncManager.getPendingOperations().size)
}

Implementar patrones offline-first requiere estrategia de caching API para móviles, asegurando disponibilidad de datos independientemente del estado de red.

Lógica de Retry

Probar mecanismos de retry bajo condiciones de red pobres es esencial para testing de rendimiento API, asegurando resiliencia contra fallos transitorios.

class RetryInterceptor(
    private val maxRetries: Int = 3,
    private val initialDelay: Long = 1000
) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        var attempt = 0
        var delay = initialDelay

        while (attempt <= maxRetries) {
            try {
                return chain.proceed(chain.request())
            } catch (e: IOException) {
                attempt++
                if (attempt > maxRetries) throw e
                Thread.sleep(delay)
                delay *= 2
            }
        }

        throw IOException("Reintentos máximos excedidos")
    }
}

@Test
fun testRetryLogic() = runTest {
    var attemptCount = 0

    val client = OkHttpClient.Builder()
        .addInterceptor { chain ->
            attemptCount++
            if (attemptCount < 3) {
                throw SocketTimeoutException("Fallo simulado")
            }
            chain.proceed(chain.request())
        }
        .addInterceptor(RetryInterceptor(maxRetries = 3))
        .build()

    val response = client.newCall(request).execute()

    assertEquals(3, attemptCount)
    assertTrue(response.isSuccessful)
}

Mejores Prácticas

  1. Prueba con perfiles de red realistas - 3G, 4G, WiFi
  2. Implementa lógica retry con backoff exponencial - Maneja fallos transitorios
  3. Usa arquitectura offline-first - Cachea datos localmente
  4. Prueba transiciones de red - WiFi ↔ celular, online ↔ offline
  5. Monitorea rendimiento de red - Rastrea latencia, throughput

Conclusión

El testing de condiciones de red asegura:

  • Degradación graceful en redes lentas
  • Funcionalidad offline
  • Mecanismos de retry apropiados
  • Consistencia de datos en estados de red

Probar varias condiciones de red previene experiencias de usuario pobres y pérdida de datos.