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.