简体   繁体   中英

React testing-library - Testing a promise that set states in the first useEffect hook

I have a useEffect hook that loads when the component is mounted, as so:

useEffect(() => {
   listFiles().then(keys => {
      setKeys(keys)
      console.log(keys)
   }).catch(err => {
        showFail(err.message)
   })
}, [])

I'm trying to test the function with react testing-library, simply using the render function:

beforeEach(() => {
  render(<Dashboard />)
})

However, when I run any test that resolves the promise and sets the state:

jest.mock('../utils/storage', () => ({
    listFiles: jest.fn(() => Promise.resolve([])),
}))

I end up with a weird warning message about using act to to wrap the event:

  Warning: An update to Dashboard 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. Learn more at .. 
        in Dashboard

      18 |     useEffect(() => {
      19 |         listFiles().then(keys => {
    > 20 |             setKeys(keys)
         |             ^
      21 |             console.log(keys)
      22 |         }).catch(err => {
      23 |             showFail(err.message)

I have tried wrapping the render in an act , but it doesn't seem to change anything.

Any suggestions as to what I'm doing wrong here? Should I be rendering in some other way?

Thanks in advance!

This error usually happens when you try to assert before the component has finished updating all state.

Note that inside listFiles you are calling setKeys(keys) and that updates the state.

You should await for the new keys(or files) to show up in the document:

expect(await findByText('some file name or key')).toBeInTheDocument();
// more asserts here

Alternatively, you can waitFor the mock to have been called (although I think the option above is better).

await waitFor(() => expect(listFilesMock).toHaveBeenCalled());
// more asserts here

The methods above should already be wrapped in act for you by the React Testing Library, so you don't need to do it


What is act() from the React docs :

When writing UI tests, tasks like rendering, user events, or data fetching can be considered as “units” of interaction with a user interface. React provides a helper called act() that makes sure all updates related to these “units” have been processed and applied to the DOM before you make any assertions.

The name act comes from the Arrange-Act-Assert pattern.


Useful links:

In my case I wasn't mocking the useEffect async function.

component.test.tsx:

test('Working useEffect hook', () => {
  const dbCallMock = jest.fn(() => Promise.resolve())
  render(<Tracks dbCallMock ={dbCallMock} />)
}

component.tsx:

export const myComponent = (props: {
  dbCallMock: Function
}) => {
  const [state, setState] = useState<string[]>([])

  const getDbState= () => 
    db.state.toArray().then(gotState => setState(gotState))

  useEffect(
    () => (props.dbCallMock ? props.dbCallMock() : getDbState()),
    []
  )
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM