If you’ve been following react community recently, you might notice the hype around the latest proposal about Hooks.

I think they are awesome😎

Dan Abramov wrote an article to give us a first glance and the problems that Hooks are trying to solve. Within a short period of time since the proposal was announced, there are so many creative custom Hooks already that you can experiment with. If you still aren’t quite sure about them, check out Ryan Florence’s amazing talk “90% Cleaner React with Hooks” at React Conf 2018.

Ryan Florence at React Conf 2018

With Hooks, we now have a pattern to tap into state and React features like context with functional components. Custom Hooks allow us to separate cross-cutting concerns from components so we can more intuitively reuse logic without Render Props or Higher Order Components. Since custom Hooks are standalone units, we want to test them independently.

Let’s get to it!

Let’s say we are writing a custom Hook to set viewport size that aligns with Bootstrap’s grid system on window resize.

// useViewport.js import { useState, useEffect } from 'react'



export default function useViewport() {

const [viewport, setViewport] = useState()



const handleResize = () => {

if (window.innerWidth > 1200) {

setViewport('extra-large')

} else if (window.innerWidth > 992) {

setViewport('large')

} else if (window.innerWidth > 768) {

setViewport('medium')

} else if (window.innerWidth > 576) {

setViewport('small')

} else {

setViewport('extra-small')

}

};



useEffect(() => {

window.addEventListener('resize', handleResize)

return () => {

window.removeEventListener('resize', handleResize)

}

})



return viewport;

}

We created a viewport variable in the state with useState and update it with useEffect when window resizes.

We can’t simply just test the useViewport function because Hooks are part components. Even though we abstract it out as a standalone function, we still rely on components’ rendering to update their states. So we’ll have to create a component to use useViewport and simulate window resize in our test.

// useViewport.test.js import React from 'react'

import { render } from 'react-testing-library'



import useViewport from './useViewport' // simulate window resize

function fireResize(width) {

window.innerWidth = width

window.dispatchEvent(new Event('resize'))

} // Test component that uses the Hook

function EffecfulComponent() {

const viewport = useViewport()

return <span>{viewport}</span>

}



test('useViewport listen to window resize and set viewport size responsively', () => {

const { container, rerender } = render(<EffecfulComponent />)

const span = container.firstChild



fireResize(320)



// useEffect is triggered after rendering.

// So we want to rerender the component to see the state change

rerender(<EffecfulComponent />)

expect(span.textContent).toBe('extra-small') fireResize(600)



rerender(<EffecfulComponent />)

expect(span.textContent).toBe('small') fireResize(800)



rerender(<EffecfulComponent />)

expect(span.textContent).toBe('medium')



fireResize(1000)



rerender(<EffecfulComponent />)

expect(span.textContent).toBe('large')



fireResize(1280)



rerender(<EffecfulComponent />)

expect(span.textContent).toBe('extra-large')

})

All we did was

1. Create a test component. 2. Trigger resize event 3. Re-render test component 4. Assert our viewport value

We re-render the test component every time after each resize event because useEffect happens after every render to handle side effects.

Here you have it! You can also find the code snippets in this gist.