Introductie
Nu je de basis matchers kent, gaan we echte React componenten testen met interactie: buttons, inputs, forms en events.
Wat ga je leren?
- Button clicks simuleren
- Input velden testen
- Formulieren testen
- State veranderingen testen
Belangrijk: userEvent importeren
We gebruiken userEvent om user interacties te simuleren. Dit import je zo:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
Input Velden Testen
Laten we een input veld testen waar je je naam kunt typen.
Het Component
Maak een bestand: src/components/NameInput.jsx
import { useState } from 'react';
function NameInput() {
const [name, setName] = useState('');
return (
<div>
<input
type="text"
placeholder="Jouw naam"
value={name}
onChange={(e) => setName(e.target.value)}
/>
{name && <p>Hallo, {name}!</p>}
</div>
);
}
export default NameInput;
De Test
Maak een test: src/components/NameInput.test.jsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import NameInput from './NameInput';
test('begint met lege input', () => {
render(<NameInput />);
const input = screen.getByPlaceholderText('Jouw naam');
expect(input).toHaveValue('');
});
test('toont begroeting na typen', async () => {
const user = userEvent.setup();
render(<NameInput />);
const input = screen.getByPlaceholderText('Jouw naam');
// Typ in het input veld
await user.type(input, 'Jan');
// Check of input de juiste waarde heeft
expect(input).toHaveValue('Jan');
// Check of begroeting verschijnt
expect(screen.getByText('Hallo, Jan!')).toBeInTheDocument();
});
user.type() tips:
await user.type(input, 'tekst')- Typ tekst in een veldscreen.getByPlaceholderText('...')- Vind input via placeholdertoHaveValue('...')- Check input waarde
Formulier Testen
Nu een volledig formulier met validatie.
Het Component
Maak een bestand: src/components/ContactForm.jsx
import { useState } from 'react';
function ContactForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [submitted, setSubmitted] = useState(false);
const handleSubmit = (e) => {
e.preventDefault();
setSubmitted(true);
};
const isValid = name.length > 0 && email.includes('@');
return (
<div>
{submitted ? (
<p>Bedankt, {name}!</p>
) : (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Naam"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button type="submit" disabled={!isValid}>
Verstuur
</button>
</form>
)}
</div>
);
}
export default ContactForm;
De Test
Maak een test: src/components/ContactForm.test.jsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import ContactForm from './ContactForm';
test('button is disabled als form leeg is', () => {
render(<ContactForm />);
const button = screen.getByText('Verstuur');
expect(button).toBeDisabled();
});
test('button is enabled als form geldig is', async () => {
const user = userEvent.setup();
render(<ContactForm />);
// Vul naam in
const nameInput = screen.getByPlaceholderText('Naam');
await user.type(nameInput, 'Jan');
// Vul email in
const emailInput = screen.getByPlaceholderText('Email');
await user.type(emailInput, 'jan@test.com');
// Check of button nu enabled is
const button = screen.getByText('Verstuur');
expect(button).not.toBeDisabled();
});
test('toont bedankt bericht na submit', async () => {
const user = userEvent.setup();
render(<ContactForm />);
// Vul form in
await user.type(screen.getByPlaceholderText('Naam'), 'Jan');
await user.type(screen.getByPlaceholderText('Email'), 'jan@test.com');
// Submit form
const button = screen.getByText('Verstuur');
await user.click(button);
// Check bedankt bericht
expect(screen.getByText('Bedankt, Jan!')).toBeInTheDocument();
// Check of form verdwenen is
expect(screen.queryByPlaceholderText('Naam')).not.toBeInTheDocument();
});
queryBy vs getBy:
getByText()- Gooit error als element niet bestaatqueryByText()- Geeft null als element niet bestaat- Gebruik
queryByom te checken dat iets NIET bestaat!
Complete Voorbeeld: Counter App
Laten we alles combineren in een counter app met plus, min en reset buttons.
Het Component
Maak een bestand: src/components/Counter.jsx
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Counter: {count}</h1>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
export default Counter;
De Test Suite
Maak een test: src/components/Counter.test.jsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Counter from './Counter';
describe('Counter component', () => {
test('begint met 0', () => {
render(<Counter />);
expect(screen.getByText('Counter: 0')).toBeInTheDocument();
});
test('verhoogt bij plus button', async () => {
const user = userEvent.setup();
render(<Counter />);
const plusButton = screen.getByText('+');
await user.click(plusButton);
expect(screen.getByText('Counter: 1')).toBeInTheDocument();
});
test('verlaagt bij min button', async () => {
const user = userEvent.setup();
render(<Counter />);
const minButton = screen.getByText('-');
await user.click(minButton);
expect(screen.getByText('Counter: -1')).toBeInTheDocument();
});
test('reset naar 0', async () => {
const user = userEvent.setup();
render(<Counter />);
// Eerst verhogen naar 5
const plusButton = screen.getByText('+');
await user.click(plusButton);
await user.click(plusButton);
await user.click(plusButton);
await user.click(plusButton);
await user.click(plusButton);
expect(screen.getByText('Counter: 5')).toBeInTheDocument();
// Dan reset
const resetButton = screen.getByText('Reset');
await user.click(resetButton);
expect(screen.getByText('Counter: 0')).toBeInTheDocument();
});
test('kan meerdere acties na elkaar doen', async () => {
const user = userEvent.setup();
render(<Counter />);
const plusButton = screen.getByText('+');
const minButton = screen.getByText('-');
// +3
await user.click(plusButton);
await user.click(plusButton);
await user.click(plusButton);
expect(screen.getByText('Counter: 3')).toBeInTheDocument();
// -1
await user.click(minButton);
expect(screen.getByText('Counter: 2')).toBeInTheDocument();
});
});
Run de tests:
npm run test
Je zou moeten zien:
PASS src/components/Counter.test.jsx
Counter component
✓ begint met 0 (24ms)
✓ verhoogt bij plus button (45ms)
✓ verlaagt bij min button (38ms)
✓ reset naar 0 (67ms)
✓ kan meerdere acties na elkaar doen (56ms)
Test Suites: 1 passed, 1 total
Tests: 5 passed, 5 total
Goed gedaan! Je hebt nu een complete component getest met meerdere interacties en state veranderingen.
Tips & Tricks
1. Gebruik screen.debug() voor debugging
test('debug voorbeeld', () => {
render(<Counter />);
// Print de hele DOM tree naar console
screen.debug();
// Print een specifiek element
const button = screen.getByText('+');
screen.debug(button);
});
2. Verschillende manieren om elementen te vinden
// Via tekst
screen.getByText('Klik hier');
// Via placeholder
screen.getByPlaceholderText('Jouw naam');
// Via role (toegankelijk!)
screen.getByRole('button');
screen.getByRole('textbox');
// Via label
screen.getByLabelText('Email');
// Via test ID (laatste optie!)
screen.getByTestId('submit-button');
3. Veelgemaakte fouten
Fout 1: Vergeten await bij userEvent
// FOUT - geen await
user.click(button); // Test faalt!
// GOED
await user.click(button);
Fout 2: Vergeten async bij test
// FOUT - geen async
test('klikt op button', () => {
await user.click(button); // Error!
});
// GOED
test('klikt op button', async () => {
await user.click(button);
});
Fout 3: getBy gebruiken voor iets dat niet bestaat
// FOUT - gooit error als niet gevonden
expect(screen.getByText('Niet bestaand')).not.toBeInTheDocument();
// GOED - gebruik queryBy
expect(screen.queryByText('Niet bestaand')).not.toBeInTheDocument();
4. Best Practices
Test gedrag, niet implementatie
// SLECHT - test state direct
expect(component.state.count).toBe(5);
// GOED - test wat de user ziet
expect(screen.getByText('Counter: 5')).toBeInTheDocument();
Gebruik toegankelijke queries
// SLECHT - test ID
screen.getByTestId('submit-btn');
// GOED - role (zoals screenreader)
screen.getByRole('button', { name: 'Verstuur' });
Wat nu?
Je kent nu de basis van Jest testing! Je kunt:
- Functies testen met matchers
- React componenten renderen
- User interacties simuleren
- State veranderingen checken