A fake request helper that resolves with your data after a random delay between 500ms and 2000ms. Good for testing loading states or prototyping before a real API exists.
const random = (min: number, max: number): number => Math.floor(Math.random() * (max - min) + min)
interface FakeRequestOptions {
timeout?: number
status?: number
}
interface FakeResponse<T> {
data: T
status: number
statusText: string
headers: Record<string, string>
}
function useFakeRequest<T>(data: T, options?: FakeRequestOptions): Promise<FakeResponse<T>> {
const { timeout = random(500, 2000), status = 200 } = options || {}
const statusText = status === 200 ? 'OK' : 'Internal Server Error'
return new Promise((resolve) => {
setTimeout(() => {
const fakeResponse: FakeResponse<T> = {
data,
status,
statusText,
headers: {
'Content-Type': 'application/json',
},
}
return resolve(fakeResponse)
}, timeout)
})
}Usage
Call it wherever you’d call fetch, passing the shape of data you expect from the real API:
const { data, status } = await useFakeRequest({ id: 1, name: 'Alice' })The random delay between 500ms and 2000ms is what makes it useful. It forces your UI to actually handle an intermediate loading state rather than rendering so fast the spinner never shows. Plenty of bugs hide in that gap.
To test error handling, pass a non-200 status:
const response = await useFakeRequest(null, { status: 500 })
if (response.status !== 200) {
// show error UI
}To pin the delay instead of randomizing it:
const response = await useFakeRequest(data, { timeout: 3000 })Limitations
This helper always resolves. It never rejects. Real network errors cause a Promise rejection, not a non-200 response. If you need to test the catch path, you’ll need a version that rejects on 5xx:
return new Promise((resolve, reject) => {
setTimeout(() => {
if (status >= 500) {
reject(new Error(statusText))
} else {
resolve(fakeResponse)
}
}, timeout)
})No AbortController support either. In real fetch calls you can cancel an in-flight request. Here the setTimeout keeps running even if the calling component unmounts. That’s fine for quick prototyping, but it will cause state updates on unmounted components if you’re not careful. Before wiring this up in tests that check cancellation behavior, swap it out for an actual mock.