I have a custom hook that downloads a file from an url, it works fine and now I trying to test its behaviour.
const useFileDownload = ({ apiResponse, fileName }) => {
const ref = useRef(null)
const [url, setUrl] = useState('')
const [name, setName] = useState('')
const download = async () => {
const { data } = await apiResponse()
const objectUrl = window.URL.createObjectURL(new Blob([data]))
setUrl(objectUrl)
setName(fileName)
ref.current?.click()
window.URL.revokeObjectURL(objectUrl)
}
return { url, ref, name, download }
}
I call like this on my component
const { ref, url, download, name } = useFileDownload({
apiResponse: () => axios.get(pdfUrl, { responseType: 'blob' }),
fileName: 'my_custom.pdf'
})
...
...
// this stay hidden on my component
<a href={url} download={name} ref={ref} />
And my test
describe('useFileDownload', () => {
it('should create refs', async () => {
window.URL.createObjectURL = jest.fn()
const mockRequest = jest.fn().mockResolvedValue({ data: 'url/to/pdf' })
const { result } = renderHook(() => useFileDownload({ apiResponse: mockRequest, fileName: 'sample.pdf' }))
act(() => {
result.current.download()
})
expect(result.current.name).toBe('sample.pdf')
expect(mockRequest).toBeCalledTimes(1)
})
})
I'm trying to mock createObjectURL, but it doesn't seem to work, and I don't know if its the right way. If the line containing window.URL fails, then the rest of the code fails on the assertion too.
There are two ways:
Option 1. Using asynchronous version act()
function
Option 2. Using waitForNextUpdate(), see example
Sometimes, a hook can trigger asynchronous updates that will not be immediately reflected in the
result.current
value. Luckily,renderHook
returns some utilities that allow the test to wait for the hook to update using async/await (or just promise callbacks if you prefer). The most basic async utility is calledwaitForNextUpdate
.
import { act, renderHook } from '@testing-library/react-hooks';
import { useFileDownload } from './useFileDownload';
describe('useFileDownload', () => {
it('should create refs', async () => {
window.URL.createObjectURL = jest.fn();
window.URL.revokeObjectURL = jest.fn();
const mockRequest = jest.fn().mockResolvedValue({ data: 'url/to/pdf' });
const { result, waitForNextUpdate } = renderHook(() => useFileDownload({ apiResponse: mockRequest, fileName: 'sample.pdf' }));
// option 1
// await act(async () => {
// await result.current.download();
// });
// option 2
result.current.download();
await waitForNextUpdate();
expect(result.current.name).toBe('sample.pdf');
expect(mockRequest).toBeCalledTimes(1);
});
});
Test result:
PASS stackoverflow/75393013/useFileDownload.test.ts (10.519 s)
useFileDownload
✓ should create refs (21 ms)
--------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------------|---------|----------|---------|---------|-------------------
All files | 100 | 50 | 100 | 100 |
useFileDownload.ts | 100 | 50 | 100 | 100 | 15
--------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 11.205 s
package version:
"jest": "^26.6.3",
"react": "^16.14.0",
"@testing-library/react-hooks": "^8.0.1"
jest.config.js
:
module.exports = {
preset: 'ts-jest/presets/js-with-ts',
testEnvironment: 'jsdom',
}