ExecBro
← усі статті

Отримайте 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.