A version of this article is also avaiable on Medium.
React hooks are an essential part of building React applications. They provide a clean way to manage state and side effects. Testing these hooks ensures that your logic is solid, but in Luau, writing tests for hooks is less fun due to the lack of a renderHook
utility in the Luau version of react-testing-library.
To help address this gap, I’ve created a small package that introduces a renderHook
function, inspired by its JavaScript counterpart.
Keep reading to see how easy it is to write tests for your hooks.
Testing a Custom Hook
To demonstrate, let’s work with a simple useCounter
hook:
local function useCounter(initialValue: number?)
initialValue = initialValue or 0
local count, setCount = React.useState(initialValue)
local function increment()
setCount(function(prevCount: number)
return prevCount + 1
end)
end
local function decrement()
setCount(function(prevCount: number)
return math.max(0, prevCount - 1)
end)
end
local function reset()
setCount(initialValue or 0)
end
return {
count = count,
increment = increment,
decrement = decrement,
reset = reset,
}
end
In the following steps, test cases will be built using jest. If you are not familiar with it, it should still be pretty easy to follow.
Step 1: Testing the default initial value
The renderHook
function initializes the hook and gives us access to its current state. For example, we can test the initial state of useCounter
like this:
local renderHook = require('@pkg/@seaofvoices/react-render-hook').renderHook
it("initializes the counter at 0 by default", function()
local renderResult = renderHook(useCounter)
local counterRef = renderResult.result
expect(counterRef.current.count).toEqual(0)
end)
The renderHook
function gives back a React ref
with what the hook returned with. In this case, the renderResult.result
ref contains the counter object returned by useCounter
.
Step 2: Testing for a different initial value
To test if the initial value can be customized, simply provide arguments to the hook when calling renderHook
. These arguments are passed directly to the hook:
it("initializes the counter at 100", function()
-- any values after useCounter will be passed to the hook
local renderResult = renderHook(useCounter, 100)
local counterRef = renderResult.result
expect(counterRef.current.count).toEqual(100)
end)
Step 3: Testing for re-renders with different initial values
To make sure re-rendering the hook does not change its state, the renderResult.rerender
can be called with new arguments to simulate an update.
it("keeps the initial value even after re-rendering with a new value", function()
local renderResult = renderHook(useCounter, 100)
local counterRef = renderResult.result
expect(counterRef.current.count).toEqual(100)
-- re-render the hook with a different value
renderResult.rerender(25)
expect(counterRef.current.count).toEqual(100)
end)
Step 4: Testing the increment function
To test the useCounter
hook properly, we need to simulate calls to increment
. For example:
local ReactTesting = require('@pkg/@jsdotlua/react-testing-library')
it("increments the counter", function()
local renderResult = renderHook(useCounter)
local counterRef = renderResult.result
ReactTesting.act(function()
counterRef.current.increment()
end)
expect(counterRef.current.count).toEqual(1)
end)
If you are not familiar with ReactTesting.act
: this ensures that React processes the increment
state change before the test continues. Without it, the state change would not complete and the test would fail.
Note: to enable act
, you will need to set the global variable __ROACT_17_MOCK_SCHEDULER__
to true
before requiring React.
Setup
The project is available on npm
, so you can easily add it with your favorite npm
compatible package manager:
yarn add -D @seaofvoices/react-render-hook
npm install @seaofvoices/react-render-hook --save-dev
Now there is one more tool in your toolbox at your disposition. If you have other questions, leave a reply to this thread.
End Notes
I work part time to dedicate time to the Luau open-source ecosystem. If you appreciate this library or other stuff I built, please consider leaving a tip I have a page on ko-fi where you can contribute or GitHub sponsors are available in any of my projects.
Useful Links
As usual, I like ending my posts with a list of various resources:
- An Awesome Luau package list
- A bunch of React hooks for Roblox at github.com/seaofvoices/react-lua-hooks
- Setup a full project in a minute with generator-luau
- Subscribe to my work on ko-fi.com/seaofvoices
- A Luau Package Standard to write compatible Luau code
- All my projects in Sea of Voices Github