3

I' trying to test a custom hook but I receive this warning message

console.error node_modules/@testing-library/react-hooks/lib/core/console.js:19
    Warning: An update to TestComponent inside a test was not wrapped in act(...).
    
    When testing, code that causes React state updates should be wrapped into act(...):
    
    act(() => {
      /* fire events that update state */
    });
    /* assert on the output */
    
    This ensures that you're testing the behavior the user would see in the browser. 

This is my custom hook

import { useState, useEffect } from 'react'

import io from 'socket.io-client'

import config from './../../../../config'

const useNotificationsSocket = (user) => {
  const [socket, setSocket] = useState(null)
  const [numUnreadMessages, setNumUnreadMessages] = useState(0)

  const configureSocket = socket => {
    socket.on('connect', () => {
      const data = {
        user: user,
      }
      socket.emit('user joined', data)
    })

    socket && socket.on('messages updated', (data) => {
      //console.log(data)
      setNumUnreadMessages(data.numUnreadMessages)
    })
  }

  useEffect(() => {
    const fetchSocket = async () => {
      const s = await io(config.nSocket.url, {transports: ['websocket']})
      configureSocket(s)
      setSocket(s)
    }

    // Check that user is not an empty object as this causes a crash.
    user && user.Id && fetchSocket()
  }, [user])

  return [socket, numUnreadMessages]
}

export { useNotificationsSocket }

and this is the test

import { renderHook, act } from '@testing-library/react-hooks'

import { useNotificationsSocket } from './../hooks/useNotificationsSocket'

jest.mock('socket.io-client')

describe('useNotificationsSocket', () => {
  it('returns a socket and numUnreadMessages', async () => {
    const user = { Id: '1' }
    const { result } = renderHook(() => useNotificationsSocket(user))
    expect(result).not.toBeNull()
  })
})

I've tried importing act and wrapping the code in a call to act but however I try to wrap the code I still get a warning and can't figure out how I should use act in this case.

johannchopin
  • 13,720
  • 10
  • 55
  • 101

1 Answers1

8

Your hook is asynchronous, so you need to await its response:

describe('useNotificationsSocket', () => {
  it('returns a socket and numUnreadMessages', async () => {
    const user = { Id: '1' }
    const { result } = renderHook(() => useNotificationsSocket(user))
    await waitFor(() => expect(result).not.toBeNull())
  })
})

Additionally, if you define multiple tests, you may encounter your original error if you fail to unmount the hook. At least this appears to be the behaviour in @testing-library/react v13.3.0. You can solve this by unmounting the hook when your test completes:

describe('useNotificationsSocket', () => {
  it('returns a socket and numUnreadMessages', async () => {
    const user = { Id: '1' }
    const { result, unmount } = renderHook(() => useNotificationsSocket(user))
    await waitFor(() => expect(result).not.toBeNull())
    unmount()
  })
})
Jason Dark
  • 323
  • 4
  • 10
  • 1
    When testing for the first return value (to be undefined/default) you no not need to wrap `expect` in awat waitFor... yet `unmount` is necessary – Mx.Wolf Oct 21 '22 at 08:13
  • 1
    Thank you, `unmount` did the trick for me. – vemund Mar 20 '23 at 15:51