2

I have the following component with a react hook containing a nested async call written in typescript.

const sampleComponent: FunctionComponent = (): ReactElement => {
    const [ detail, setDetail ] = useState<detailObject>(undefined);

    useEffect(() => {
        getConfigs();
    }, [ detail != undefined ]);

    const getConfigs = () => {
        getConfig().then((configResp: any) => {
            if(response.status === 200) {
                getConfigDetail(configResp.data.id).then((response) => {
                    if(response.status === 200) {
                        setDetail(response.data)
                    }
                })
            }
        })
    }

    return (
        //Some UI related to the responses.
    )
}

I test this with the following test.

const configList = jest.spyOn(api, "getConfigList");
const configDetail = jest.spyOn(api, "getConfig");

configList.mockImplementation(() => {
    return Promise.resolve(configListRequestResponse); // these are sample responses
});

configDetail.mockImplementation(() => {
    return Promise.resolve(configDetailsRequestResponse); // these are sample responses
})

test("Test proper rendering config component", async () => {
   await act(async () => {
      render(
          <Provider store={ store }>
              <sampleComponent/>
          </Provider>
      );
      await waitFor(() => expect(configList).toHaveBeenCalledTimes(1));
      await waitFor(() => expect(configDetail).toHaveBeenCalledTimes(1));
      expect(screen.getByTestId("remote-configs")).toBeInTheDocument();
   });
});

When I run this test, I get the following error.

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

    > 95 |       setDetail(response.data);
         |       ^

What am I doing wrong here?

1 Answer 1

1

you should create a renderWithState function in which you define the provider as a wrapper like so

import React, { FC, ReactElement } from 'react';
import { Provider } from 'react-redux';
import { render } from '@testing-library/react';
import { createTestStore } from '../redux';
import { AppStore } from '../redux/types';
import { createStore } from 'redux';
import { rootReducer } from './reducers';
const createTestStore = (store: AppStore) => createStore(rootReducer, store);

export const renderWithState = (Component: ReactElement, options?: { state: AppStore }) => {
  const store = createTestStore({dummystate:"hello"});
  const Wrapper: FC = ({ children }) => <Provider store={store}>{children}</Provider>;

  return render(Component, { wrapper: Wrapper });
};

describe('index tsx', () => {
  it('it should render mocked state provider', () => {
    const Hello = () => <div>hello world</div>;
    renderWithState(<Hello />, { state: initStore });
  });
});

also to test async behaviour do it like below

  it('it renders text dummy dummies on click', async () => {
    const { queryByRole, getByText } = renderWithState(
      <Router>
        <CustomDropDown/>
      </Router>,
      {
        state: sb.store,
      },
    );

    const toggler = queryByRole('dropdown-toggle');

    await act(async () => {
      fireEvent.click(toggler!);
      await waitForElement(() => getByText('dummy'));
      await waitForElement(() => getByText('dummies'));
    });
  });

hope this helps you on your way

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.