Post

Testing i React

Testing i React

Test af React komponenter med Vitest og React Testing Library

For at udføre tests har vi brug for følgende pakker:

  • Vitest: Test framework.
  • JSDOM: Simulerer DOM i tests.
  • React Testing Library: Testværktøjer specifikt til React.

Installer disse med:

1
yarn add -D vitest jsdom @testing-library/react

Konfiguration af Vitest

I filen vite.config.js tilføjes en test-konfiguration:

1
2
3
4
5
6
export default defineConfig({
  test: {
    global: true,
    environment: 'jsdom',
  },
});
  • global: Gør testmetoder tilgængelige uden yderligere import.
  • environment: Angiver, at testen skal køre i en “jsdom”-miljø.

Tilføj desuden et test-kommando til package.json:

1
2
3
"scripts": {
  "test:unit": "vitest --root src/"
}

Udvidelse af expect med DOM-metoder

For at få metoder som toBeInTheDocument() og toHaveTextContent() til DOM-elementer, skal du installere pakken:

1
yarn add -D @testing-library/jest-dom

Opret derefter en fil setupTest.js i projektets rodkatalog, og tilføj følgende:

1
2
3
4
import { expect } from 'vitest';
import matchers from '@testing-library/jest-dom/matchers';

expect.extend(matchers);

Til sidst registreres denne fil i vite.config.js under feltet setupFiles:

1
2
3
test: {
  setupFiles: './setupTest.js',
}

Eksempel på test af en React-komponent

Vi tester en komponent kaldet Movies, der viser en liste af film og har søgefunktion.

Movies-komponentens kode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
export const Movies = () => {
  const { movies } = useMovies();
  const { searchTerm, setSearchTerm, filteredItems: filteredMovies } = useSearch(movies);
  
  return (
    <section>
      <div>
        <label htmlFor="search">Search</label>
        <input
          type="search"
          id="search"
          value={searchTerm}
          data-testid="search-input-field"
          onChange={(event) => setSearchTerm(event.target.value)}
        />
      </div>
      <ul data-testid="movies-list">
        {filteredMovies.map((movie, index) => (
          <li key={index}>
            <article>
              <h2>{movie.title}</h2>
              <p>Release on: {movie.release_date}</p>
              <p>Directed by: {movie.director}</p>
              <p>{movie.opening_crawl}</p>
            </article>
          </li>
        ))}
      </ul>
    </section>
  );
};

Testopsætning

Vi bruger vi.spyOn() fra Vitest til at overvåge og mocke hooks useMovies og useSearch:

1
2
3
4
5
6
7
import * as useMoviesHooks from '../hooks/useMovies';
import * as useSearchHooks from '../hooks/useSearch';

describe('Movies', () => {
  const useMoviesSpy = vi.spyOn(useMoviesHooks, 'useMovies');
  const useSearchSpy = vi.spyOn(useSearchHooks, 'useSearch');
});

Mock værdierne returneres som:

1
2
3
4
5
6
useMoviesSpy.mockReturnValue({ movies: items });
useSearchSpy.mockReturnValue({
  searchTerm: '',
  setSearchTerm: vi.fn(),
  filteredItems: items,
});

Testrendering

Renderer komponenten og verificerer, at listen over film vises korrekt:

1
2
3
4
5
6
7
import { render, screen } from '@testing-library/react';
import { Movies } from './Movies';

it('should render the list of movies', () => {
  const { getByTestId } = render(<Movies />);
  expect(getByTestId('movies-list').children.length).toBe(items.length);
});

Test af søgefunktion

For at teste søgefeltet bruger vi:

  • fireEvent.change() til at simulere inputændringer.
  • act() for at sikre, at ændringerne reflekteres korrekt i DOM’en.
1
2
3
4
5
6
7
8
9
10
11
12
import { act, fireEvent } from '@testing-library/react';

it('should change the filtered items when the search term changes', () => {
  const { getByTestId } = render(<Movies />);
  const searchInput = getByTestId('search-input-field');
  
  act(() => {
    fireEvent.change(searchInput, { target: { value: 'Wars' } });
  });

  expect(getByTestId('movies-list').children.length).toBe(1);
});

Oprydning

For at sikre isolerede tests ryddes mocks og DOM efter hver test:

1
2
3
4
5
6
7
8
9
10
afterEach(() => {
  useMoviesSpy.mockClear();
  useSearchSpy.mockClear();
});

import { cleanup } from '@testing-library/react';

afterEach(() => {
  cleanup();
});
This post is licensed under CC BY 4.0 by the author.