Тестування роботи мобільного додатку у офлайн-режимі
Офлайн-режим — одна з тих функцій, яку заявляють у вимогах та тестують поверхово. «Без інтернету не працює» — та окей. Але для додатків, якими користуються у метро, у поїздках, у зонах слабого покриття, це критично. Гірше «не працює» — працює неправильно: показує застарілі дані без мітки часу, втрачає введені дані, мовчки ошибається.
Що перевіряємо
Три основні сценарії:
Запуск без мережі. Що видит користувач, відкриваючи додаток без інтернету? Правильно: останні кешовані дані з міткою часу («оновлено 2 години тому»). Неправильно: білий екран або повідомлення «немає з'єднання» без жодного контенту.
Втрата з'єднання під час використання. Користувач переглядав ленту, з'єднання зникло. Уже завантажений контент повинен залишатися доступним. Незавершені дії — збережені як чорновики або поставлені у чергу.
Відновлення з'єднання. Дані синхронізуються, чергу відкладених дій виконується, конфлікти розв'язуються. Користувач видит актуальні дані без ручного оновлення.
Локальне кешування: що та як
На iOS — NSURLCache для HTTP-ответов (якщо сервер повертає правильні Cache-Control заголовки), Core Data або Realm для структурованих даних, UserDefaults для невеликих налаштувань. Для медіа — FileManager з власним каталогом кешу.
Core Data з NSPersistentCloudKitContainer дає синхронізацію через CloudKit «з коробки», але конфлікти розв'язує примітивно — last-write-wins. Для складної логіки конфліктів реалізуємо кастомний merge policy.
На Android — Room для структурованих даних, DataStore для налаштувань, Cache-Control через OkHttp для HTTP-кешу:
val cacheSize = 10 * 1024 * 1024L // 10 MB
val cache = Cache(context.cacheDir, cacheSize)
val okHttpClient = OkHttpClient.Builder()
.cache(cache)
.addNetworkInterceptor { chain ->
val response = chain.proceed(chain.request())
response.newBuilder()
.header("Cache-Control", "public, max-age=300") // 5 хвилин
.build()
}
.addInterceptor { chain ->
val request = if (isNetworkAvailable()) {
chain.request()
} else {
chain.request().newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=${60 * 60 * 24}") // 24 години з кешу
.build()
}
chain.proceed(request)
}
.build()
only-if-cached + max-stale — читаємо з кешу, навіть якщо дані застарілі, поки офлайн.
Чергу відкладених дій
Дії користувача у офлайні повинні виконатися при відновленні мережі. Не «втеруть з помилкою», а збережуться та виконаються.
Android — WorkManager з setRequiredNetworkType(NetworkType.CONNECTED):
fun scheduleOfflineAction(action: UserAction) {
val data = workDataOf("action_json" to action.toJson())
val request = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.setInputData(data)
.build()
WorkManager.getInstance(context).enqueue(request)
}
Дія зберігається у базу WorkManager, виконується як тільки появиться мережа, переживе перезавантаження пристрою.
iOS — BackgroundTasks framework (BGProcessingTask) або простіший варіант: локальна черга у Core Data, спроба виконання при applicationDidBecomeActive та при зміні мережі через NWPathMonitor.
Тестові сценарії та інструменти
Відключення мережі у тесті:
iOS симулятор: Hardware → Network Link Conditioner → 100% Loss або xcrun simctl status_bar + емуляція через Network Link Conditioner profile.
Android емулятор: adb shell svc wifi disable && adb shell svc data disable. Відновлення: enable.
У автотестах (Detox):
it('shows cached data when offline', async () => {
// Завантажуємо дані при наявності мережі
await element(by.id('feed_list')).waitForVisible();
// Відключаємо мережу (тільки на Android через adb)
await device.setNetworkConditions({ offline: true });
// Перезапускаємо та перевіряємо кеш
await device.reloadReactNative();
await expect(element(by.id('cached_banner'))).toBeVisible();
await expect(element(by.id('feed_list'))).toBeVisible(); // кеш працює
});
Конфлікти при синхронізації
Користувач відредагував запис офлайн. Інший користувач змінив цю саму запис онлайн. При відновленні з'єднання — конфлікт.
Стратегії: last-write-wins (простіше, але втрачає дані), server-wins (передбачуваного, але ігнорує офлайн-правки), merge (правильно, але складно). Для більшості додатків достатньо показати користувачу діалог вибору версії.
Строки
2–3 дні — тестування по чеклисту офлайн-сценаріїв, оцінка корректності кешування та черги дій, звіт. Вартість розраховується індивідуально.







