0

I'm using Jest to do some integration testing on my React App (AWS, Redux Toolkit, MSW). I have a custom setup files that I am using in my tests as per react-testing-library instructions (https://testing-library.com/docs/react-testing-library/setup/).

testsUtils.js

import { App } from 'containers';
import { BrowserRouter } from 'react-router-dom';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import i18n from './i18n';
import { persistedReducer } from 'store';
import { render } from '@testing-library/react';

const getStore = (store) => {
  const str = { ...store };
  return configureStore({
    preloadedState: {
      ...str,
    },
    reducer: persistedReducer,
  });
};

const renderTestApp = ({ route = '/', store }) => {
  const str = getStore(store);
  window.history.replaceState({}, '', route);
  return render(
    <Provider store={str}>
      <BrowserRouter>
        <I18nextProvider i18n={i18n}>
          <App />
        </I18nextProvider>
      </BrowserRouter>
    </Provider>,
  );
};

export * from '@testing-library/react';

export { renderTestApp as render };

This file allow me to render the app in a test, pass a route and an initial store to test a specific screen. However, when an API call is made (I'm mocking the AWS requests using MSW) this warning shows up :

A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks. Active timers can also cause this, ensure that .unref() was called on them.

On top of that, running 42 tests from 5 tests suites is really slow the first time, it takes around 180 seconds to complete and there are been instances where launching the tests plainly freezes my machine.

FileSelection.test.js

import { render, screen, waitFor } from 'testsUtils';

import userEvent from '@testing-library/user-event';

describe('FileSelection', () => {
  beforeEach(async () => {
    render({
      route:
        '/selection/human?id=kixUziFOWKpBhnTrknaA6w&des=SUM',
      store: {
        users: {
          secondary: 'MSW msw',
          main: 'JSW jsw',
        },
        files: {},
      },
    });
    const title = await screen.findByText('Selection');
    expect(title);
  });

  test('FileSelection screen renders', async () => {
    await waitFor(() => {
      expect(screen.getByText('Selection')).toBeInTheDocument();
      expect(
        screen.getByRole('button', { name: 'Continue' }),
      ).toBeInTheDocument();
    });
  });
}

App.js

import {
  FileSelection,
} from 'containers';
import { Route, Routes } from 'react-router-dom';

import styled from '@emotion/styled';
import useApp from './hook/useApp';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';

const AppLoading = styled.div`
  align-items: center;
  display: flex;
  height: 100vh;
  justify-content: center;
`;

const AppContainer = styled.div`
  flex: 1;
`;

const App = (props) => {
  const {
    location,
    processing,
  } = useApp(props);
  const { t } = useTranslation();

 return (
    <ThemeProvider>
      {processing ? (
        <AppLoading>
          <Loading size={240} />
        </AppLoading>
      ) : (
        <AppContainer>
          <Routes>
            {tabsList.map((tab, index) => {
              return (
                <Route
                  element={
                    <Selection/>
                  }
                  key={t('Selection.path', {
                    slug: tab.slug,
                  })}
                  path={t('Selection.path', {
                    slug: tab.slug,
                  })}
                />
              );
            })}
          </Routes>
        </AppContainer>
      )}
    </ThemeProvider>
  );

export default App;

useApp.js

import {
  apiGetData,
  getId,
} from 'store';
import { useDispatch, useSelector } from 'react-redux';
import { useEffect, useState } from 'react';
import { useLocation, useNavigate } from 'react-router';

import _ from 'lodash';
import slugify from 'slugify';
import styled from '@emotion/styled';
import { useTranslation } from 'react-i18next';

const BoExchangeTitle = styled.div`
  flex: 1;
`;

const BoExchangeTitleContainer = styled.div`
  align-items: flex-end;
  display: flex;
  justiycontent: space-between;
`;

const useApp = (props) => {
  const dispatch = useDispatch();
  const headings = useSelector(getHeadings);
  const location = useLocation();
  const navigate = useNavigate();
  const id =
    props?.id || useSelector(getId);
  const { t } = useTranslation();
  const [processing, setProcessing] = useState(false);

  useEffect(async () => {
    const queryParams = new URLSearchParams(location.search);
    const searchId = queryParams.get('id');
    const des = props?.des || queryParams.get('des');
    let i = id;
    if (searchId && searchId !== id) {
      i = searchId;
    }
    if (destination) {
      dispatch(setRecipient(destination));
    }
    if (i) {
      try {
        setProcessing(true);
        await dispatch(apiGetData({ id })); //Api Call right here
        dispatch(setId(id));
        setProcessing(false);
      } catch (error) {
        setProcessing(false);
        navigate(t('TechnicalError.path'));
        console.log(error);
      }
    } else {
      navigate(t('TechnicalError.path'));
    }
  }, []);

  return {
    location,
    processing,
  };
};

export default useApp;

apiGetData

export const apiGetData = ({ id }) => {
  return async (dispatch) => {
    try {
      const response = await API.get(
        "Endpoint gateway name",
        "Endpoint path",
        {
          headers: {
            'private key': 'private key value',
          },
          response: true,
        },
      );
      if (response.status === 200) {
        const { data } = response;
        return data;
      }
    } catch (error) {
      const { data, status, statusText } = error.response || {};
      console.log('error', error);
      throw {
        data,
        status,
        statusText,
      };
    }
  };
};

To me, it looks like this mocked AWS call is not detected as finished which causes the performance issues... Any idea on how to tackle this ? (Sorry if some code is missing, I had to cut a lot since I can't share the original one). To clarify, the application is working fine on it's own, the tests are the issue.

I tried commenting the API call in useApp.js which makes the test run as expected. The test run fast and no warning logs in the console. I tried running the tests with --runInBand and --detectOpenHandles which didn't print anything useful. I tried disabling MSW which didnt change anything as expected.

ichiin
  • 1

0 Answers0