25 lines
1.3 KiB
Markdown
25 lines
1.3 KiB
Markdown
|
|
---
|
||
|
|
name: Mocking fetch in api-server route tests
|
||
|
|
description: How to mock upstream fetch without breaking the test client's own request to the in-process Express server
|
||
|
|
---
|
||
|
|
|
||
|
|
When testing an api-server route that itself calls `fetch` (e.g. provider model
|
||
|
|
discovery / connection tests), the route tests spin up the real Express app via
|
||
|
|
`app.listen(0)` and hit it with `fetch(`${baseUrl}/api/...`)`. The app runs in the
|
||
|
|
SAME process as the test, so `vi.spyOn(globalThis, "fetch")` intercepts BOTH the
|
||
|
|
test client's request to the server AND the route's upstream provider call.
|
||
|
|
|
||
|
|
**Rule:** capture `const realFetch = globalThis.fetch.bind(globalThis)` BEFORE
|
||
|
|
mocking, and in the mock implementation delegate any request whose URL starts with
|
||
|
|
the test server's `baseUrl` back to `realFetch`. Only synthesize a response for the
|
||
|
|
upstream (non-localhost) URL.
|
||
|
|
|
||
|
|
**Also:** the spy records the localhost passthrough call too, so don't assert on
|
||
|
|
`mock.calls[0]` — filter to the call whose URL does NOT start with `baseUrl` to find
|
||
|
|
the real upstream request. Likewise `not.toHaveBeenCalled()` / `toHaveBeenCalledTimes(1)`
|
||
|
|
must be expressed in terms of the filtered upstream calls, not the raw spy.
|
||
|
|
|
||
|
|
**Why:** without the passthrough, the test client's request to the in-process server
|
||
|
|
gets the synthetic upstream body (or the thrown error), so every test fails before the
|
||
|
|
route logic runs.
|