• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Mockowanie funkcji w testach aplikacji

0 głosów
412 wizyt
pytanie zadane 5 czerwca 2021 w JavaScript przez poldeeek Mądrala (5,980 p.)

Cześć, chcę przetestować swój komponent React. Korzysta on z funkcji fetcher() do pobierania danych z REST API. Funkcja ta znajduje się w innym pliku, więc testując ten komponent chciałem po prostu zmockować ten plik w taki sposób, aby dla  każdego testu zwrócił inną wartość.

Najlepiej, aby były zwracane promise, ponieważ dla jednego z testów chciałbym zwrócić Promise.reject(). 

Póki co napisałem taki test:

 

jest.mock('../../utils/fetcher', () => ({
  fetcher: jest
    .fn()
    .mockReturnValueOnce('first call')
    .mockReturnValueOnce('second call')
    .mockReturnValue('default'),
  readData: jest.fn(),
}));

test('Basic render. fetch pending - the loading', async () => {
  const component = render(<Courses />);
  await waitForElementToBeRemoved(() => component.getByTitle('loader'));
});

test('Basic render, fetch success', async () => {
  const component = render(<Courses />);
  await waitFor(() => component.findByText('CollapsibleTable'));
  expect(component.baseElement).toMatchSnapshot();
});

test('Basic render, fetch error', async () => {
  const component = render(<Courses />);
  //await waitFor(() => component.findByText('Something went wrong.'));
  expect(component.baseElement).toMatchSnapshot();
});


I już na tym etapie pojawia się problem, ponieważ nie potrafię dojść dlaczego dla każdego z testów fetcher() mimo wszystko zwraca 'first call. 

 

Komponent:
 

const Courses: React.FC = () => {
  const { data, error } = useSWR(
    'https://some-api.pl',
    fetcher
  );

  console.log(data, error);

  if (error) {
    return (
      <CoursesContainer>
        <Error>Something went wrong.</Error>
      </CoursesContainer>
    );
  }

  if (!data) return <Loader title="loader" />;

  return (
    <CoursesContainer>
      <CollapsibleTable courses={data} />
    </CoursesContainer>
  );
};

export default Courses;

 

fetcher:
 


const fetcher = (url: string) => {
  return fetch(url)
    .then((response) => response.json())
    .then((data: Array<IFetchData>) => readData(data));
};

Funkcja readData() bierze tablicę data i trochę ją przerabia, zwracając inną tablicę tylko z tymi danymi, których potrzbuje. Nie ma tam żadnych asynchronicznych działań.

 

biblioteka do wykonywania zapytań - swr

2 odpowiedzi

0 głosów
odpowiedź 5 czerwca 2021 przez poldeeek Mądrala (5,980 p.)

Doszedłem to takiego kodu:
 

jest.mock('../../utils/fetcher', () => ({
  fetcher: jest
    .fn()
    .mockImplementationOnce(() => Promise.resolve('data'))
    .mockImplementationOnce(() => Promise.resolve('data'))
    .mockImplementationOnce(() => Promise.reject('error')),
  readData: jest.fn(),
}));

test('Basic render. fetch pending - the loading', async () => {
  console.log('test1');
  const component = render(<Courses />);
  await waitForElementToBeRemoved(() => component.getByTitle('loader'));
  expect(component.baseElement).toMatchSnapshot();
});

test('Basic render, fetch success', async () => {
  console.log('test2');
  const component = render(<Courses />);
  await waitFor(() => component.findByText('CollapsibleTable'));
  expect(component.baseElement).toMatchSnapshot();
});

test('Basic render, fetch error', async () => {
  console.log('test3');
  const component = render(<Courses />);
  await waitFor(() => component.findByText('Something went wrong.'));
  expect(component.baseElement).toMatchSnapshot();
});


I działa to lepiej, jednak komponent pobiera tak jakby stare dane z poprzedniego testu.. Najlepiej widać to na tym przykadzie console.logów:

 

console.log
    test1

      at src/components/__tests__/Courses.tsx:23:11

  console.log
    undefined undefined

      at Courses (src/components/Courses.tsx:14:11)

  console.log
    data undefined

      at Courses (src/components/Courses.tsx:14:11)

  console.log
    test2

      at src/components/__tests__/Courses.tsx:30:11

  console.log
    data undefined

      at Courses (src/components/Courses.tsx:14:11)

  console.log
    test3

      at src/components/__tests__/Courses.tsx:37:11

  console.log
    data undefined

Co jest dziwne, bo przy pierwszym teście funkcja fetch wykonuje się 2 razy (pierwszy raz zwraca undefinded, undefinded), natomiast w kolejnych testach wykonuje się ona tylko raz i zwraca dane, które były zwrócone jako ostatnie w poprzednim teście.

Error dla ostaniego testu:

0 głosów
odpowiedź 5 czerwca 2021 przez poldeeek Mądrala (5,980 p.)

Dodałem czyszczenie cache w swr, ponieważ przez to robił się problem z zostawionymi danymi z poprzednich testów. 

Doszedłem do wniosku, że generalnie nie da się tego przetestować.
Kod, który był najbliżej rozwiązania:
 

jest.mock('../../utils/fetcher', () => ({
  fetcher: jest
    .fn()
    .mockImplementationOnce(() => Promise.resolve('data'))
    .mockImplementationOnce(() => Promise.resolve('data2'))
    .mockImplementationOnce(() => Promise.reject('error')),
  readData: jest.fn(),
}));

afterEach(() => {
  cache.clear();
});

test('Basic render. fetch pending - the loading', async () => {
  console.log('test1');
  const component = render(<Courses />);
  await waitForElementToBeRemoved(() => component.getByTitle('loader'));
  expect(component.baseElement).toMatchSnapshot();
});

test('Basic render, fetch success', async () => {
  console.log('test2');
  const component = render(<Courses />);
  await waitFor(() => component.findByText('CollapsibleTable'));
  expect(component.baseElement).toMatchSnapshot();
});

test('Basic render, fetch error', async () => {
  console.log('test3');
  const component = render(<Courses />);
  await waitFor(() => component.findByText('Something went wrong.'));
  expect(component.baseElement).toMatchSnapshot();
});


Według mnie problem polega na tym, że dla komponentu renderowanego w każdym z tych testów metoda fetcher będzie wywoływana po raz pierwszy, czyli każdy z nich użyje mock'a - .mockImplementationOnce(() => Promise.resolve('data')). Więc nie ma opcji, żeby któryś użył w końcu  .mockImplementationOnce(() => Promise.reject('error')).

Podobne pytania

+1 głos
1 odpowiedź 398 wizyt
+1 głos
0 odpowiedzi 289 wizyt
pytanie zadane 10 marca 2021 w JavaScript przez poldeeek Mądrala (5,980 p.)
0 głosów
1 odpowiedź 619 wizyt
pytanie zadane 17 kwietnia 2020 w JavaScript przez michal_php Stary wyjadacz (13,700 p.)

93,753 zapytań

142,713 odpowiedzi

323,356 komentarzy

63,347 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj

Twierdza Linux. Bezpieczeństwo dla dociekliwych

Aby uzyskać rabat -10%, użyjcie kodu pasja-linux, wpisując go w specjalne pole w koszyku.

...