React Testing Library does not find elements using getAllByTestId after initial render
Asked Answered
A

2

8

I have a very simple component where I am trying to simulate an API call to get some movies with a slight delay.

I want to write a test which tests that the movies are gathered and then rendered to screen.

I am trying to use screen.getAllByTestId to do this, however it always fails. It is as if it doesn't re-render and therefore does not get the updated change.

I have added a testid onto the elements and can see these in the DOM.

Can anyone help as to why this isn't finding them after they appear?

enter image description here

Here is the full component code...

import './App.css';
import { useEffect, useState } from 'react';

function App() {
  const [movies, setMovies] = useState([]);

  useEffect(() => {
    // simulate API call to get
    setTimeout(() => {
      const movies = [{ title: 'Titanic' }, { title: 'Back To The Future' }];
      setMovies(movies);
    }, 1000);
  }, []);

  return (
    <div>
      {movies.length > 0 && (
        <div>
          {movies.map((x) => (
            <div data-testid='movies'>{x.title}</div>
          ))}
        </div>
      )}
    </div>
  );
}

export default App;

Here is the full test code...

import { render, screen } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  render(<App />);
  const movieTiles = screen.getAllByTestId('movies');
  expect(movieTiles).toHaveLength(2);
});

Here is the error from the test

enter image description here

Aborn answered 20/7, 2021 at 19:7 Comment(0)
A
5

You should use Fake Timers when your code uses timers (setTimeout, setInterval, clearTimeout, clearInterval).

Use jest.advanceTimersByTime(1000) to move ahead in time by 1000ms.

Don't forget act() helper function:

When writing UI tests, tasks like rendering, user events, or data fetching can be considered as “units” of interaction with a user interface. react-dom/test-utils provides a helper called act() that makes sure all updates related to these “units” have been processed and applied to the DOM before you make any assertions:

Since we run the setState function that will change the component state 1000ms ahead of time, we must wrap this operation (jest.advanceTimersByTime(1000)) in the act() function.

Otherwise, you will get warning:

Warning: An update to App inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):

E.g.

App.jsx:

import React, { useEffect, useState } from 'react';

function App() {
  const [movies, setMovies] = useState([]);

  useEffect(() => {
    setTimeout(() => {
      const movies = [{ title: 'Titanic' }, { title: 'Back To The Future' }];
      setMovies(movies);
    }, 1000 * 10);
  }, []);

  return (
    <div>
      {movies.length > 0 && (
        <div>
          {movies.map((x, idx) => (
            <div key={idx} data-testid="movies">
              {x.title}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

export default App;

App.test.jsx:

import { render, screen, act } from '@testing-library/react';
import React from 'react';
import App from './App';

describe('68460159', () => {
  test('renders learn react link', async () => {
    jest.useFakeTimers();
    render(<App />);
    act(() => {
      jest.advanceTimersByTime(1000 * 10);
    });
    const movieTiles = screen.getAllByTestId('movies');
    expect(movieTiles).toHaveLength(2);
    jest.runOnlyPendingTimers();
    jest.useRealTimers();
  });
});

test result:

 PASS  examples/68460159/App.test.jsx (7.878 s)
  68460159
    ✓ renders learn react link (33 ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |                   
 App.jsx  |     100 |      100 |     100 |     100 |                   
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        8.678 s, estimated 10 s
Alvin answered 21/7, 2021 at 2:6 Comment(3)
Brilliant, so will this be the same for if I do API calls? I was just using a timer above to simulate but I get the same issue when I use API call which has a slight delayAborn
@Aborn It's different for API calls, you need to set up a mock API server using msw and use await waitFor() to wait for the component state change. See testing-library.com/docs/react-testing-library/example-introAlvin
Ahh ok thank you, so you can’t use a component which has a real API call inside is that right? I’m a bit confused at how I can use a real component which is using a real API call and substitute this for a mock one for testing purposes?Aborn
O
0

I've experienced this issue without having setTimeouts in hooks or anything. What worked for me was transitioning from getAllBy* to await findAllBy* and making the whole test async of course. Everything else stayed the same and it suddenly passed 👌✅

Sharing just in case someone is loosing hours with the issue above as I was.

Overboard answered 31/8, 2022 at 15:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.