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();
});