React-hooks-testing-library: How to test useEffect which will not trigger re-render

Created on 14 Apr 2020  路  3Comments  路  Source: testing-library/react-hooks-testing-library

package

dep:

  • "@testing-library/react-hooks": "3.2.1"
  • "react": "16.9.0"
  • "react-test-renderer": "16.13.1"

dev/dep:

  • "jest": "23.6.0",
  • "sinon": "9.0.2",
    (here we use sinon to stub the some functions)

code

  • myHook.js
function useMyHook(){
  const [isGood, setIsGood] = useState(false);
  useEffect(() => {
    async function getIsGood(){
      const isGood = await apiClient.getIsGood();
      setIsGood(isGood);
    }
  }, []);
  return {isGood}
}
  • test.specs.js
import sinon from 'sinon';
import useMyHook from '...';
import apiClient from '...';
import { renderHook } from '@testing-library/react-hooks';

describe('', () => {
  const sandbox = sinon.createSandbox();
  it('', async () => {
    // arrange
    // always resolve false;
    const stub = sandbox.stub(apiClient, 'getIsGood');
    stub.resolves(false);

    // act
    const { result, waitForNextUpdate } = renderHook(() => useMyHook());
    await waitForNextUpdate();
    expect(result.current.isGood).toBeFalsy();
  });
});

expected result

test passes

actual result

Async timeout. Codes stop at await waitForNextUpdate();

possible reason

I read the docs and found one comment here https://github.com/testing-library/react-hooks-testing-library/issues/211#issuecomment-548538370. It seems like the component will not be re-rendered since the state(isGood) is not changed(always set to false). So that await waitForNextUpdate() is never resolved.

I still want to test some behaviours like apiClient.getIsGood is called and isGood will be set properly by the result from the apiClient.getIsGood.

However with waitForNextUpdate I can't do it since component is not re-rendered and without waitForNextUpdate, the expected assertions is evaluated before the useEffect which is logically wrong.

Is there a solution to this problem?

Thanks!

bug

Most helpful comment

I fixed this for myself by waiting for the assertion to become true, i.e.

 await wait(() => expect(spy).toHaveBeenCalledTimes(1))

All 3 comments

Hi @Kiiiwiii,

sorry for the delay in responding. Unfortunately if there is no render, the promise won't resolve (it is literally waiting for the next render to fire).

You could put a small timeout in the test and catch the timeout error so it doesn't wait forever for an render that is not coming:

    const { result, waitForNextUpdate } = renderHook(() => useMyHook());
    try {
      await waitForNextUpdate({ timeout: 100 } );
    } catch(err) {
      expect(err.timeout).toBeTruthy();
    }
    expect(result.current.isGood).toBeFalsy();

_Note: completely untested_

@mpeyper thanks for your response. This walk around works!

I fixed this for myself by waiting for the assertion to become true, i.e.

 await wait(() => expect(spy).toHaveBeenCalledTimes(1))
Was this page helpful?
0 / 5 - 0 ratings