How to create bindings for @testing-library/react-hooks

I am trying to create bindings for @testing-library/react-hooks. I have a playground where most of this is working but I am struggling to understand how to bind to Typescript parameters of type any

function renderHook(callback: (props?: any) => any, options?: RenderHookOptions): RenderHookResult

I have created a type that let’s me pass in parameters of any built-in type but only by binding to a single record type.

type initialValue<'props> = {initialValue: 'props}

Can someone explain how I can generalize the callback parameter so that I may pass random properties as is shown in the testing-library documentation

import { renderHook, act } from ‘@testing-library/react-hooks’
import useCounter from ‘./useCounter’
test(‘should reset counter to updated initial value’, () => {
const { result, rerender } = renderHook(({ initialValue }) => useCounter(initialValue), {
initialProps: { initialValue: 0 }
})
rerender({ initialValue: 10 })
act(() => {
result.current.reset()
})
expect(result.current.count).toBe(10)
})

All of the code is viewable on github
Oh, and by the way, does anyone know how to add Enabled Libraries to the playground’s settings? I would like to open @glennsl/rescript-jest and @ryyppy/rescript-promise.

Appreciative of any suggestions!

Unfortunately we can’t right now, because we’d need to compile rescript & side-load pre-bundled JS library code on the fly. Right now we only precompiled libraries for e.g. rescript-react. Maybe we could automate / generalize this process on a github repo with a CI job and allow end users to pick those libraries in the playground later on in the future.

Hi @stephanie

For that callback argument, could you declare its type for each test rather than having a global shared type? This would allow you to specify a different argument in each test. For example:

module TestHookWithProps = {
  type hookProps = {initialValue: int}
  // Test code here 
}

Also, you might be able to simplify your bindings. I haven’t tested the following code, but maybe it has some ideas for you:

type result<'return> = {
  all: array<'return>,
  current: 'return,
  error: Js.Exn.t,
}

type renderHookResult<'props, 'return> = {
  result: result<'return>,
  rerender: 'props => unit,
  unmount: unit => unit,
}
type callback<'props, 'return> = 'props => 'return
type options<'props> = {initialProps: 'props}

@module("@testing-library/react-hooks")
external renderHookWithOptions: (
  callback<'props, 'return>,
  options<'props>,
) => renderHookResult<'props, 'return> = "renderHook"

@module("@testing-library/react-hooks")
external renderHook: callback<'props, 'return> => renderHookResult<'props, 'return> = "renderHook"

And your tests might then look like:

module TestSimpleHook = {
  let hookCb = () => Counter.useCounter()
  let {result} = renderHook(hookCb)
  let count = result.current["count"]
}

module TestHookWithProps = {
  type hookProps = {initialValue: int}
  let hookCb = ({initialValue}) => Counter.useCounter(~initialValue, ())
  let {result, rerender} = renderHookWithOptions(hookCb, {initialProps: {initialValue: 0}})
  let newProps = {initialValue: 13}
  rerender(newProps)
  act(() => result.current["reset"]())
  let count = result.current["count"]
}

Also, if you specify record types for your hook return values, it will make using results a bit nicer. For example for your useCounter function if the return type was:

type counter = {count: int, increment: unit => unit, reset: unit => unit}

Then you can use code such as:

act(() => result.current.reset())
let count = result.current.count

Thanks @kevanstannard. Your suggestion makes sense though I think I need to do organize this as a functor. I’ll post a solution when I have something working.

Thanks @ryyppy. It makes sense that you can only do the libraries you control.

Thank you again @kevanstannard. Using a functor for this seems like overkill but it worked and helped me better understand functors.

Your key observation was simply moving the prop and result type definitions into the test components.

I updated the playground and perhaps will release to NPM once I have added tests for testing-library advanced hook functionality and SSR.

3 Likes