Отримайте 500, не чіпаючи бекенд: мокання мережі в React Native (ExecBro)
get_network_requestsget_screen_statenetwork_conditionnetwork_mocknetwork_replayscan_metrotap
У кожному React Native застосунку є обробка помилок, роботи якої ніхто ніколи не бачив.
Не тому, що її в принципі не тестували, а тому, що дістатися до неї справді важко. Не можна попросити продакшн-API віддати 500. Null у avatarUrl, що валить екран профілю, трапляється в одного клієнта з Бельгії. Логіка повторних спроб виглядає правильною, і це весь доказ на її користь.
Тож зазвичай результат підробляють: напряму надсилають action помилки або перемикають boolean у store. Так можна дістатися екрана помилки, тому й здається, що все працює. Але при цьому пропускаються конструктор запиту, парсер відповіді, блок catch, повторна спроба й toast, тобто кожен рядок між «запит не вдався» і «користувач щось бачить», а баг живе саме там.
Інструменти мокання ExecBro натомість змінюють відповідь. Застосунок робить свій справжній запит, отримує задану вами відповідь і далі виконує весь власний код.
| Інструмент | Що робить |
|---|---|
network_mock |
Замінює відповідь або змінює справжню |
network_condition |
Імітує офлайн / повільну / нормальну мережу |
network_replay |
Повторно надсилає запит, який застосунок уже зробив |
Усе нижче це справжній вивід із симулятора iOS та емулятора Android.
Цикл
Підключіться один раз, а далі чотири кроки:
mcp__execbro__scan_metro {}Metro scan results:
Port 8081: Found 1 device(s)
- Connected to com.gifted.production (iPhone Air) (iPhone Air)
додати → відтворити → подивитися → очистити. Це весь процес.
1. Чи обробляє цей екран 500?
Найпоширеніше питання й зазвичай найшвидший спосіб знайти баг.
mcp__execbro__network_mock {"action": "add", "url": "/api/orders", "status": 500, "body": "{\"error\":\"server error\"}"}Added [m1] ANY /api/orders -> replace on iPhone Air. Survives reload_app. Clear with network_mock({action:"clear"}).
Тепер відтворіть: перейдіть на екран і прочитайте, що зробив застосунок:
mcp__execbro__tap {"text": "Orders"}
mcp__execbro__get_screen_state {}url це звичайний підрядок, тож /api/orders збігається з https://api.example.com/v2/api/orders/17. Запит застосунку так і не доходить до мережі: він отримує ваш 500 і виконує власний шлях помилки.
Якщо get_screen_state показує спінер, що ніколи не зникає, або порожній список без жодного повідомлення, це і є знахідка. На екрані немає стану помилки. Ви довели це приблизно за п’ятнадцять секунд, без змін на бекенді й без feature-флага.
2. А якщо цього поля немає?
replace повертає заготовлене тіло, і це правильно для «сервер упав», але неправильно для «одне поле null». Для цього потрібна справжня відповідь з однією зміною. Це tamper:
mcp__execbro__network_mock {"action": "add", "url": "itunes.apple.com/lookup", "mode": "tamper", "remove": ["resultCount"], "set": {"injected.flag": true}}За лаштунками ExecBro робить справжній запит другим, прихованим XHR, змінює JSON і передає результат вашому застосунку. Ось як застосунок його читає:
{"status":200,"parsed":true,"hasResultCount":false,"injected":{"flag":true},"realResultsStillThere":1}Справжній масив results вцілів, resultCount зник, вкладене injected.flag створено, і застосунок розібрав усе без заперечень. І set, і remove приймають шляхи через крапку, тож set: {"data.subscription.status": "expired"} покаже вам UI простроченої підписки без простроченого акаунта.
Якщо відповідь не JSON, tamper пропускає її без змін і каже вам про це: ви отримуєте попередження, а не зіпсоване тіло й помилку парсингу, яку довелося б налагоджувати.
3. Чи справді працює моя повторна спроба?
times спрацьовує обмежену кількість разів, а потім пропускає трафік. З times: 1 перша спроба падає, а друга проходить: саме для такої ситуації і існує логіка повторних спроб.
mcp__execbro__network_mock {"action": "add", "url": "/api/sync", "status": 503, "times": 1}Два послідовні запити зі справжнього пристрою:
attempt1=503
attempt2=200
А правило повідомляє, що зробило:
mcp__execbro__network_mock {"action": "list"}[m4] ANY /api/sync -> replace 503 (times:1, spent) hits=1
Якщо застосунок сам відновився, повторна спроба працює. Якщо показав помилку й зупинився, повторних спроб немає взагалі, і тепер ви це знаєте, а не припускаєте.
4. Що буде без мережі?
mcp__execbro__network_condition {"mode": "offline"}Offline: every JS-originated request now fails with 'Network request failed'. (iPhone Air)
NetInfo: not-installed — the app does not use @react-native-community/netinfo. Request failure is unaffected.
Survives reload_app. Undo with network_condition({mode:"normal"}).
Наступний запит застосунку падає так само, як при справжньому розриві з’єднання:
rejected: Network request failed
Другий рядок важливий. Багато застосунків показують офлайн-UI за useNetInfo(), а не за невдалим запитом, тож offline також намагається пропатчити NetInfo, а потім перевіряє, чи патч справді спрацював, повідомляючи один зі станів patched, reads-patched-only або not-installed. Інструмент каже, чого досяг, а не припускає. Падіння запитів працює в будь-якому разі.
Поверніть усе, коли закінчите:
mcp__execbro__network_condition {"mode": "normal"}5. Чи є стан завантаження?
Локальні API відповідають за 200 мс, тому ніхто не помічає відсутнього скелетона, доки його не помітить користувач у потязі.
mcp__execbro__network_condition {"mode": "slow", "latencyMs": 3000}Той самий запит до і після:
status=200 elapsedMs=255 ← normal
status=200 elapsedMs=3048 ← slow
В іншому відповідь не змінюється, тож це суто перевірка таймінгу. Трьох секунд досить, щоб упіймати відсутній скелетон, кнопку відправки, яку можна натиснути двічі, поки перший запит ще в дорозі, або спінер, що взагалі не з’являється.
6. Чому цей POST повернув 422?
Застосунок надіслав форму, а API її відхилив. Замість того щоб знову проходити форму й пробувати з одним зміненим полем, повторіть перехоплений запит:
mcp__execbro__get_network_requests {"status": 422}
mcp__execbro__network_replay {"requestId": "js-a1b2-7"}
mcp__execbro__network_replay {"requestId": "js-a1b2-7", "body": "{\"quantity\": 1}"}Змінюйте по одному полю, і менш ніж за хвилину знайдете, проти чого насправді заперечує бекенд. Повтор іде через власний мережевий стек застосунку, тож несе той самий токен авторизації, ту саму довіру TLS і ту саму конфігурацію проксі, що й оригінал: це той самий запит, а не реконструкція.
Замоканий трафік ніколи не буває невидимим
Агент, що налагоджує збій, який сам і спричинив, це єдиний спосіб, у який така функція стає гіршою за марну. Тому змінений трафік себе оголошує. Замокані рядки позначено:
[js-wm68-6] 11:36:16 PM GET 503 7ms https://api.example.com/orders [MOCK m1]
А кожне читання мережі має банер, поки активне хоч одне правило:
[1 mock rule(s) active (1 on iPhone Air) — responses below may be altered.
network_mock({action:"list"}) to inspect, {action:"clear"} to remove]
Приберіть за собою
Правила діють для кожного пристрою окремо і переживають reload_app. Так задумано: мок, що зникає під час перезавантаження, був би марним для налагодження будь-чого на шляху старту, а саме там живуть найскладніші мережеві баги.
Ціна в тому, що забуте правило тихо впливає на вашу наступну сесію, а симптом виглядає точнісінько як справжній баг. Тож завершуйте так:
mcp__execbro__network_mock {"action": "clear"}
mcp__execbro__network_condition {"mode": "normal"}Якщо згодом щось здається зламаним, першим перевірте банер.
Коли правило не спрацьовує
Спершу перевірте list. Діє перше правило, що збіглося, тож широке правило, додане раніше, затіняє вузьке, додане пізніше. Правило з hits=0, яке мало б спрацювати, майже завжди затінене тим, що над ним.
Перевірте шаблон. За замовчуванням url це підрядок. Для точного збігу обгорніть його слешами, і він стане регулярним виразом: "/\\/orders\\/\\d+$/". Шаблони перевіряються перед збереженням: вираз із катастрофічним бектрекінгом буде відхилено, а не відправлено заморожувати JS-потік вашого застосунку.
Перевірте рівень. Мокання охоплює HTTP, що йде з JS: fetch, axios, усе, що працює через XMLHttpRequest. Трафік нативних модулів (нативні SDK аналітики, завантаження <Image>) повністю його оминає.
У чому суть
Ідеться не про те, щоб обійтися без staging-середовища. Ідеться про розрив між «екран помилки рендериться» і «шлях помилки працює», а баги саме там: парсер, що розраховує на поле, повторна спроба, яка ніколи не спрацьовує, кнопка, яку можна натиснути двічі під час повільного запиту.
Тепер до кожного з них один виклик інструмента, і виконується ваш справжній код.
Ці інструменти з’явилися в ExecBro 2.7.0. Усі параметри перелічено в довіднику інструментів, а повний письмовий посібник (зокрема шляхи tamper, збіг за регулярними виразами та взаємодія з SDK) лежить у docs/network-mocking.md.