Lushmoney
Lushmoney

Reputation: 480

Jest Unit Test for React Hooks component with act() error

I have a problem here that's stumping me, and I don't know how to go about fixing this. I have a very simple React Hooks functional component that uses the useState hook, but I am having an issue with writing a test case for it (in Jest), with @testing-library/react.

Here is the meat of the component:

// MyComponent.jsx

import InputComponent from '/some/folder/InputComponent';

...

export function MyComponent() {

  const [randomStateVariable, setRandomStateVariable] = useState('');

  ...

  const handleChange = value => setRandomStateVariable(value);

  ...

  return (
    <InputComponent
      handleChange={handleChange}
      input={{
        value: randomStateVariable || ''
      }}
    />
  );

}

And then for my unit test file, I am doing the following:

// MyComponent.test.js

import renderer from 'react-test-renderer';
import { render } from '@testing-library/react';

...

import MyComponent from 'MyComponent';

...

it('runs handleChange', () => {
  
  const props = {
    actions: {
      someAction: jest.fn()
    },
    input: 'some input value'
  };
  
  ...

  const component = renderer.create(<MyComponent {...props} />);
  const inputSelect = component.root.findByType(InputComponent).props;

  inputSelect.handleChange(props.input);

  expect(props.actions.someAction).toHaveBeenCalledWith(props.input);

});

Now, the tests are all passing (I am using @testing-library/react in other test cases in the file), but I am getting the following error when running the test suite:

When testing, code that causes React state updates should be wrapped into act(...):

I am not sure how to go about correcting this. I don't have the @testing-library/react-hooks package installed, and I am unsure of where I should be importing act from. Should I be using the react-test-renderer for this, and import act from that package, or should I be importing it from @testing-library/react?

I don't know how to write a test case, or modify the existing test case, in using act, to get rid of the console error. Is there anyone that can assist, or point me in the right direction for something like this?

Thank you very much!

Upvotes: 0

Views: 3988

Answers (1)

Lin Du
Lin Du

Reputation: 102207

TestRenderer.act() is same with act() of react-dom/test-utils module. So you just need to import it from react-test-renderer package. @testing-library/react is another react test library, you only need to choose one of the two. It will be confusing to use together, because each test library has its test method and process, which may not be compatible.

act():

When writing UI tests, tasks like rendering, user events, or data fetching can be considered as “units” of interaction with a user interface. react-dom/test-utils 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:

inputSelect.handleChange(props.input) trigger an updated operation, it should be put in act().

E.g.

MyComponent.test.tsx:

import React, { useState } from 'react';
import InputComponent from './InputComponent';

export function MyComponent({ actions, input }) {
  const [randomStateVariable, setRandomStateVariable] = useState('');

  const handleChange = (value) => setRandomStateVariable(value);

  return (
    <InputComponent
      handleChange={handleChange}
      input={{
        value: randomStateVariable || '',
      }}
    />
  );
}

MyComponent.test.tsx:

import React from 'react';
import renderer, { act } from 'react-test-renderer';
import { MyComponent } from './MyComponent';
import InputComponent from './InputComponent';

describe('65786455', () => {
  it('runs handleChange', () => {
    const props = {
      actions: {
        someAction: jest.fn(),
      },
      input: 'some input value',
    };

    const component = renderer.create(<MyComponent {...props} />);
    const inputSelect = component.root.findByType(InputComponent).props;
    expect(inputSelect.input.value).toBe('');
    act(() => {
      inputSelect.handleChange(props.input);
    });
    expect(component.root.findByType(InputComponent).props.input.value).toBe('some input value');
  });
});

unit test result:

 PASS  examples/65786455/MyComponent.test.tsx
  65786455
    ✓ runs handleChange (24 ms)

--------------------|---------|----------|---------|---------|-------------------
File                | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
--------------------|---------|----------|---------|---------|-------------------
All files           |     100 |      100 |     100 |     100 |                   
 InputComponent.tsx |     100 |      100 |     100 |     100 |                   
 MyComponent.tsx    |     100 |      100 |     100 |     100 |                   
--------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        5.878 s

Upvotes: 3

Related Questions