APIがfreezeされてmockできない時はtransport layerをmockする
一部のruntimeは、高レベルAPIをpageへ注入し、アプリcodeが走る前にfreezeします。その場合、test中でもAPIを直接monkey patchできません。
実用的なfallbackは1段下へ降りることです。APIが内部で使うtransportを見つけ、そこをinterceptします。
症状
testやautomation codeがruntime APIをwrapしようとします。
const originalInvoke = window.__APP_INTERNALS__.invoke;
window.__APP_INTERNALS__.invoke = async (command, args) => {
if (command === "load_projects") {
return [{ id: "p1", name: "Example" }];
}
return originalInvoke(command, args);
};
しかしcallはreal backendへ行く。Object.defineProperty がthrowする。assignmentが成功したように見えても効果がない。
descriptorを確認します。
console.log(
Object.getOwnPropertyDescriptor(window.__APP_INTERNALS__, "invoke")
);
writable: false かつ configurable: false なら、そのpropertyはfreezeされています。そのlayerでは置き換えられません。
transportを探す
高レベルAPIは、最終的に低レベルtransportを使います。
runtime.invoke(command, args)
-> fetch(...)
runtime.send(message)
-> postMessage(...)
client.call(payload)
-> WebSocket.send(...)
DevTools、source inspection、一時的なnetwork loggerでtransportを確認します。
たとえばIPC風APIが内部URLへ fetch() しているかもしれません。
http://ipc.localhost/load_projects
高レベルAPIがfreezeされていても fetch がwrap可能なら、fetch をinterceptします。
fallthrough付きでfetchをinterceptする
mockは狭くし、それ以外は通します。
const originalFetch = window.fetch.bind(window);
window.fetch = async (input, init) => {
const url = typeof input === "string" ? input : input.url;
if (url.startsWith("http://ipc.localhost/load_projects")) {
return new Response(
JSON.stringify([{ id: "p1", name: "Example" }]),
{
status: 200,
headers: { "content-type": "application/json" },
}
);
}
return originalFetch(input, init);
};
fallthroughが重要です。test対象のcommandだけをmockし、無関係なrequestは本来の挙動へ通します。
app初期化前にinstallする
transport interceptionは、app codeが初期化する前に入れる必要があります。browser automationではinit scriptを使います。
await page.addInitScript(() => {
const originalFetch = window.fetch.bind(window);
window.fetch = async (input, init) => {
const url = typeof input === "string" ? input : input.url;
if (url.startsWith("http://ipc.localhost/load_projects")) {
return new Response(JSON.stringify([]), {
status: 200,
headers: { "content-type": "application/json" },
});
}
return originalFetch(input, init);
};
});
appが元のtransportを先にcaptureすると、後から置き換えても効かないことがあります。
判断チェック
Object.getOwnPropertyDescriptorで高レベルAPIがfreezeされているか確認する- 実transportを特定する:
fetch、postMessage、WebSocket、custom protocol、native bridge - test contextでtransportをwrapできるか確認する
- app初期化前にwrapperをinstallする
- target route/messageだけをmockする
- unknown requestはoriginal transportへ通す
- 開発中だけlogし、最終testからnoisy logを消す
限界
transport-layer mockは強力ですが、integration bugを隠すために使うものではありません。
向く場面です。
- runtimeが高レベルAPI置換を禁止する
- test対象がfrontend behavior
- deterministic backend responseが必要
- CIでreal backendが使えない
real integrationを優先する場面です。
- transport protocol自体をtestしている
- auth、permission、security policyが対象
- request serializationが疑わしい