Проблема с тестированием на ошибку в компоненте React с помощью response-query и axios. Тестирование с помощью библиотеки тестирования React, Jest, MSW
Я тестирую компонент реакции, который должен отображать либо счетчик, либо ошибку, либо список выбранных рецептов. Мне удалось протестировать этот компонент на загрузку и при успешном получении данных. У меня проблема с ошибкой тестирования. Я создал тестовый сервер с msw, у которого есть обработчик маршрута, который возвращает ошибку. Я использую axios для запросов к серверу. Я думаю, проблема здесь: axios делает 3 запроса, и до последнего ответа useQuery возвращает isLoading=true, isError=false. Только после этого возвращает isLoading=false, isError=true. Итак, в моем тесте на ошибку screen.debug() показывает счетчик, а errorMessage возвращает ошибку, потому что он не находит визуализированный элемент с текстом «error», этот компонент должен показывать, когда произошла ошибка. Что я могу с этим поделать? У меня заканчиваются идеи.
РЕДАКТИРОВАТЬ:
- Я обнаружил, что в useQuery есть параметр «повторить попытку», который по умолчанию составляет 3 запроса. Я до сих пор не знаю, что делать с запросами на повторную попытку компонентов в моем тесте.
Я новичок в тестировании реакции и машинописи.
Из RecipeList.test.tsx:
import { setupServer } from 'msw/node';
import { rest } from 'msw';
import { QueryClient, QueryClientProvider, QueryCache } from 'react-query';
import RecipeList from './RecipeList';
const server = setupServer(
rest.get(`${someUrl}`, (req, res, ctx) =>
res(ctx.status(200), ctx.json(data))
)
);
const queryCache = new QueryCache();
const RecipeListWithQueryClient = () => {
const client = new QueryClient();
return (
<QueryClientProvider client={client}>
<RecipeList />
</QueryClientProvider>
);
};
// Tests setup
beforeAll(() => server.listen());
afterAll(() => server.close());
afterEach(() => server.resetHandlers());
afterEach(() => {
queryCache.clear();
});
describe('RecipeList', () => {
//some tests that pass
describe('while error', () => {
it('display error message', async () => {
server.use(
rest.get(`${someUrl}`, (req, res, ctx) => res(ctx.status(404)))
);
render(<RecipeListWithQueryClient />);
// FOLLOWING CODE RAISE ERROR
const errorMessage = await screen.findByText(/error/i);
await expect(errorMessage).toBeInTheDocument();
});
});
});
Код компонента:
Из RecipeList.tsx:
import { fetchRecipes } from 'api';
const RecipeList: React.FC = () => {
const { data, isLoading, isError } = useQuery<
RecipeDataInterface,
Error
>('recipes', fetchRecipes);;
if (isLoading) {
return (
<Spinner>
<span className="sr-only">Loading...</span>
</Spinner>
);
}
if (isError) {
return (
<Alert >Error occured. Please try again later</Alert>
);
}
return (
<>
{data?.records.map((recipe: RecipeInterface) => (
<RecipeItem key={recipe.id} recipe={recipe.fields} />
))}
</>
);
};
2 ответа
Решение, которое я нашел, - установить индивидуальное значение тайм-аута для теста.
В RTL для асинхронного метода waitFor в соответствии с документами :
«Тайм-аут по умолчанию составляет 1000 мс, что позволит вам не превысить тайм-аут Jest по умолчанию, равный 5000 мс».
Мне пришлось установить параметр тайм-аута для асинхронной функции RTL, но этого было недостаточно, так как я получал сообщение об ошибке:
«Превышен тайм-аут в 5000 мс для теста. Используйте jest.setTimeout (newTimeout), чтобы увеличить значение тайм-аута, если это длительный тест». Мне пришлось изменить значение тайм-аута, установленное Jest для теста. Это можно сделать двумя способами, как описано в этом комментарии:
"1. Добавьте к нему третий параметр. Т.е. it ('работает медленно', () => {...}, 10000)2. Запишите jest.setTimeout(10000); в файл с именем src / setupTests. js, как указано здесь ".
Наконец, мой тест выглядит так:
it('display error message', async () => {
server.use(
rest.get(getUrl('/recipes'), (req, res, ctx) =>
res(ctx.status(500), ctx.json({ errorMessage: 'Test Error' }))
)
);
render(<RecipeListWithQueryClient />);
expect(
await screen.findByText(/error/i, {}, { timeout: 10000 })
).toBeInTheDocument();
}, 10000);
Здесь можно использовать различные асинхронные методы, такие как waitFor и waitForElementToBeRemoved, и все они могут принимать объект {timeout: 10000} в качестве аргумента.
Это решение работает для меня, но я бы хотел его улучшить, чтобы тесты не занимали много времени. Просто еще не понял, как это сделать.
Я использовал QueryClient, чтобы отключить повторную попытку. И setupServer для имитации сетевого подключения
import { QueryClient, QueryClientProvider} from 'react-query';
import { setupServer } from 'msw/node'
import { rest } from 'msw'
const server = setupServer(
rest.get('https://reqres.in/api/users', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json(userSuccessResponseJson()
),
)
}),)
beforeAll(() => server.listen())
afterEach(() => {
server.resetHandlers();
queryClient.clear();
})
afterAll(() => server.close())
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retryDelay: 1,
retry:0,
},
},
})
const Wrapper = ({ children }) => (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
test('Renders Error text when backend server response with error', async () => {
server.use(
rest.get('https://reqres.in/api/users', (req, res, ctx) => {
return res(ctx.status(403))
})
);
render(<Wrapper><CompanyList />)</Wrapper>);
const linkElement = screen.getByText(/Loading/i);
expect(linkElement).toBeInTheDocument();
await waitFor(() => {
const fetchedText = screen.getByText(/Error:/i);
expect(fetchedText).toBeInTheDocument();
})
});