Проблема с тестированием на ошибку в компоненте 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();
    })
  });
Другие вопросы по тегам