Siehe: `Website/Relaunch 2026 01/components/__tests__/MultilingualChat.behavior.test.tsx` (API-Contract + State-Persistence Muster)
Install
npx skillscat add svenja-dev/claude-code-skills/skills-behavior-testing Install via the SkillsCat registry.
Behavior Testing Skill
Trigger
- Nach Feature-Implementation
- Wenn Tests nur DOM-Rendering pruefen ("smoke tests")
- Wenn Code-Review Luecken in der Test-Abdeckung findet
/behavior-testsSlash-Command
Problem
Standard-Tests (via AI-Generation oder Copilot) pruefen oft nur "wird gerendert?", nicht "funktioniert es?". Das fuehrt zu:
- Buttons ohne Funktionalitaet bestehen alle Tests
- API-Aufrufe mit falschen Argumenten werden nie erkannt
- State-Verlust bei Navigation/Re-Mount bleibt unsichtbar
- Fehlende Error-UI wird nie getestet
4 Test-Kategorien (PFLICHT)
1. API-Contract Tests
Was: Verifiziere, dass API-Funktionen mit den richtigen Argumenten aufgerufen werden.
Warum: Falsche Argumente (fehlende Language-Parameter, falsche Endpoints) sind unsichtbar in Rendering-Tests.
// SCHLECHT: Prueft nur DOM
it('sends message', async () => {
await sendMessage('hello');
expect(screen.getByText('hello')).toBeInTheDocument(); // DOM only!
});
// GUT: Prueft API-Contract
it('sends message with correct language', async () => {
await sendMessage('hello');
expect(mockApi).toHaveBeenCalledWith(
'hello',
expect.any(Array), // history
'en', // language code
);
});Checkliste:
- Mock der API-Funktion mit
vi.fn() - Pruefe Aufruf-Argumente mit
toHaveBeenCalledWith - Pruefe Anzahl der Aufrufe mit
toHaveBeenCalledTimes - Pruefe was bei API-Fehler passiert (Error-UI sichtbar?)
2. State-Persistence Tests
Was: Verifiziere, dass State nach Re-Mount, Page-Refresh oder Auth-Wechsel korrekt bleibt.
Warum: sessionStorage/localStorage-Bugs, Zustand-Resets bei Login/Logout.
// Prueft sessionStorage-Persistenz
it('persists counter to sessionStorage', async () => {
render(<Chat />);
await sendMessage('test');
expect(sessionStorage.getItem('key')).toBe('1');
});
// Prueft Auth-Transition
it('resets counter on login', () => {
sessionStorage.setItem('key', '5');
useAuthStore.setState({ isAuthenticated: true });
// Effect should reset
expect(sessionStorage.getItem('key')).toBe('0');
});Checkliste:
- sessionStorage/localStorage vor und nach Aktionen pruefen
- Auth-State-Wechsel testen (logged-in -> logged-out und umgekehrt)
- Zustand-Store direkt mit
setStatemanipulieren fuer Edge-Cases - Default-State nach Store-Reset pruefen
3. Error-Boundary Tests
Was: Verifiziere, dass Fehler dem User angezeigt werden und die App nicht crasht.
Warum: Fehlende Error-UI ist die haeufigste UX-Luecke.
// API-Fehler -> Error-UI
it('shows error on checkout failure', async () => {
mockPost.mockRejectedValueOnce(new Error('Server error'));
fireEvent.click(checkoutButton);
await waitFor(() => {
expect(screen.getByText(/error|fehler/i)).toBeInTheDocument();
});
});
// 204 No Content -> kein Crash
it('handles 204 No Content', async () => {
mockFetch({ ok: true, status: 204, text: () => Promise.resolve('') });
const result = await apiClient.get('/api/logout');
expect(result).toEqual({});
});Checkliste:
- API-Rejection -> Error-Message sichtbar
- Leere Responses (204, empty body) -> kein Crash
- Rate-Limit (429) -> Retry-Info angezeigt
- Non-JSON Error-Body -> graceful degradation
4. Security-Boundary Tests
Was: Verifiziere Input-Validation, Auth-Gates und Redirect-Sicherheit.
Warum: XSS-Praevention, Open-Redirect-Verhinderung, Rate-Limit-Enforcement.
// Input-Laenge
it('rejects input over 2000 chars', async () => {
await sendMessage('a'.repeat(2001));
expect(mockApi).not.toHaveBeenCalled();
expect(screen.getByText(/maximum/i)).toBeInTheDocument();
});
// URL-Validation
it('blocks non-stripe.com redirect URLs', async () => {
mockPost.mockResolvedValueOnce({ url: 'https://evil.com/steal' });
fireEvent.click(checkoutButton);
await waitFor(() => {
expect(screen.getByText(/error/i)).toBeInTheDocument();
});
});
// CSRF-Token
it('attaches CSRF token to POST but not GET', async () => {
document.cookie = 'csrf_token=abc123';
await apiClient.post('/api/data', {});
expect(fetchMock).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
headers: expect.objectContaining({ 'X-CSRF-Token': 'abc123' }),
}),
);
});Checkliste:
- Input-Laenge-Limits werden client-seitig enforced
- Auth-Gate blockiert unauthentifizierte User
- Plan-Gate blockiert Free-Plan bei Premium-Features
- Redirect-URLs werden auf erlaubte Domains geprueft
- CSRF-Token wird bei POST angehaengt, nicht bei GET
- Credentials: 'include' auf allen Requests
Implementierungs-Workflow
- Identifiziere Luecken: Lies bestehende Tests. Pruefe ob sie nur
toBeInTheDocument()nutzen. - Mock die APIs:
vi.mock()fuer Service-Module,vi.fn()fuer einzelne Funktionen. - Setze State direkt:
useAuthStore.setState({...})statt ueber UI navigieren. - Pruefe Aufrufe:
toHaveBeenCalledWith()ist wichtiger alstoBeInTheDocument(). - Teste Fehlerpfade: Jeder
try/catchim Produktionscode braucht einen Test.
Dateibenennung
# Bestehende Rendering-Tests behalten:
ComponentName.test.tsx
# Neue Behavior-Tests daneben:
ComponentName.behavior.test.tsx
# Oder in describe-Bloecken trennen:
describe('rendering', () => { ... }); // bestehend
describe('behavior', () => { ... }); // neu
describe('error handling', () => { ... }); // neuAnti-Patterns
| Anti-Pattern | Problem | Loesung |
|---|---|---|
Nur toBeInTheDocument() |
Prueft nicht ob Button funktioniert | toHaveBeenCalledWith() |
| Kein API-Mock | Test ruft echte API auf | vi.mock() + vi.fn() |
| Happy-Path only | Fehler-UI wird nie getestet | mockRejectedValueOnce() |
| State-Annahmen | Store wird nicht zurueckgesetzt | beforeEach + setState |
| Kein Auth-Kontext | Tests laufen nur als eingeloggt | Teste beide Zustaende |
Referenz-Implementierung
Siehe: Website/Relaunch 2026 01/services/__tests__/apiClient.test.ts (Error-Boundary + Security-Boundary Muster)
Siehe: Website/Relaunch 2026 01/components/__tests__/MultilingualChat.behavior.test.tsx (API-Contract + State-Persistence Muster)