React Testing Library provides a utility renderHook to test hooks. It injects a hook within a fake component that t created to manage the lifecycle of a component for you.

test('hook: prevents default on the first click, and does not on the second', async () => {  
	const { result } = await renderHook(() => useDoubleCheck())  
  
	expect(result.current.doubleCheck).toBe(false)  
  
	const mock = vi.fn()  
  
	const event = new MouseEvent('click', {  
		bubbles: true,  
		cancelable: true,  
	}) as unknown as React.MouseEvent<HTMLButtonElement>  
  
	act(() => result.current.getButtonProps({ onClick: mock }).onClick(event))  
  
	expect(result.current.doubleCheck).toBe(true)  
	expect(mock).toHaveBeenCalledOnce()  
	expect(event.defaultPrevented).toBe(true)  
  
	mock.mockClear()  
  
	const event2 = new MouseEvent('click', {  
		bubbles: true,  
		cancelable: true,  
	}) as unknown as React.MouseEvent<HTMLButtonElement>  
  
	act(() => result.current.getButtonProps({ onClick: mock }).onClick(event2))  
  
	expect(result.current.doubleCheck).toBe(true)  
	expect(mock).toHaveBeenCalledOnce()  
	expect(event2.defaultPrevented).toBe(false)  
})  

There’s one gotcha. When using renderHook, you have to remember to always deconstruct the object to get result property. It is because of how JS works or something.