A test asserting that a fetch happened once fails under React 18 development mode, and the test is wrong rather than the framework.
// fails in 18 under StrictMode
expect(fetchSpy).toHaveBeenCalledTimes(1)
// what the double invocation is telling you: the effect
// has no cleanup, so a real remount would leak.
// the fix is in the component, not the test:
useEffect(() => {
const c = new AbortController()
fetch(url, { signal: c.signal }).then(setData)
return () => c.abort()
}, [url])
Loosening the assertion to toHaveBeenCalled makes the test pass and keeps the defect, which is the wrong direction. Testing the outcome — that the state ends up correct — rather than the call count is the assertion that is robust under both modes and is a better test regardless.