Moderne Testing-Praxis - Eine React Todo-App mit Vite und Cypress
In der heutigen Frontend-Landschaft ist qualitativ hochwertiges Testing ein nicht verhandelbarer Bestandteil des Entwicklungsprozesses. In diesem umfassenden Tutorial zeige ich dir, wie du eine React Todo-App mit Vite (statt Create React App) erstellen und mit Cypress umfassend testen kannst.
Inhaltsverzeichnis
Abschnitt betitelt „Inhaltsverzeichnis“- Einführung
- Projekt-Setup mit Vite
- Entwicklung der Todo-App
- Cypress Installation und Konfiguration
- End-to-End Tests mit Cypress
- Tipps für effektives Testing
- Fazit
Einführung
Abschnitt betitelt „Einführung“Warum Vite statt Create React App? Vite bietet schnellere Build-Zeiten durch den Einsatz von ES-Modulen im Entwicklungsmodus und optimiertes Bundling für die Produktion. In Kombination mit Cypress für zuverlässiges E2E-Testing erhalten wir einen modernen, effizienten Entwicklungs-Workflow.
Projekt-Setup mit Vite
Abschnitt betitelt „Projekt-Setup mit Vite“Schritt 1: Projekt initialisieren
Abschnitt betitelt „Schritt 1: Projekt initialisieren“# Projekt mit Vite erstellennpm create vite@latest react-todo-cypress -- --template react
# In das Projektverzeichnis wechselncd react-todo-cypress
# Abhängigkeiten installierennpm installSchritt 2: Projektstruktur organisieren
Abschnitt betitelt „Schritt 2: Projektstruktur organisieren“Lass uns eine übersichtliche Struktur anlegen:
mkdir src/componentsmkdir src/stylestouch src/styles/App.css src/components/TodoItem.jsx src/components/TodoForm.jsxEntwicklung der Todo-App
Abschnitt betitelt „Entwicklung der Todo-App“Schritt 3: App-Komponente erstellen
Abschnitt betitelt „Schritt 3: App-Komponente erstellen“Bearbeite die Datei src/App.jsx:
import { useState, useEffect } from 'react'import TodoForm from './components/TodoForm'import TodoItem from './components/TodoItem'import './styles/App.css'
function App() { const [todos, setTodos] = useState(() => { const savedTodos = localStorage.getItem('todos') return savedTodos ? JSON.parse(savedTodos) : [] })
useEffect(() => { localStorage.setItem('todos', JSON.stringify(todos)) }, [todos])
const addTodo = (text) => { if (text.trim() !== '') { setTodos([...todos, { id: Date.now(), text, completed: false }]) } }
const toggleTodo = (id) => { setTodos(todos.map(todo => todo.id === id ? { ...todo, completed: !todo.completed } : todo )) }
const deleteTodo = (id) => { setTodos(todos.filter(todo => todo.id !== id)) }
return ( <div className="app-container" data-cy="app-container"> <h1>React Todo App</h1> <TodoForm onAddTodo={addTodo} />
<div className="todo-list" data-cy="todo-list"> {todos.length === 0 ? ( <p className="empty-message" data-cy="empty-message">Keine Todos vorhanden. Füge welche hinzu!</p> ) : ( todos.map(todo => ( <TodoItem key={todo.id} todo={todo} onToggle={toggleTodo} onDelete={deleteTodo} /> )) )} </div> </div> )}
export default AppSchritt 4: Komponenten erstellen
Abschnitt betitelt „Schritt 4: Komponenten erstellen“Erstelle src/components/TodoForm.jsx:
import { useState } from 'react'
function TodoForm({ onAddTodo }) { const [text, setText] = useState('')
const handleSubmit = (e) => { e.preventDefault() onAddTodo(text) setText('') }
return ( <form onSubmit={handleSubmit} data-cy="todo-form"> <input type="text" value={text} onChange={(e) => setText(e.target.value)} placeholder="Was muss erledigt werden?" data-cy="todo-input" /> <button type="submit" data-cy="add-button"> Hinzufügen </button> </form> )}
export default TodoFormErstelle src/components/TodoItem.jsx:
function TodoItem({ todo, onToggle, onDelete }) { return ( <div className={`todo-item ${todo.completed ? 'completed' : ''}`} data-cy="todo-item" > <span onClick={() => onToggle(todo.id)} data-cy="todo-text" className="todo-text" > {todo.text} </span> <button onClick={() => onDelete(todo.id)} data-cy="delete-button" className="delete-button" > Löschen </button> </div> )}
export default TodoItemSchritt 5: Styling hinzufügen
Abschnitt betitelt „Schritt 5: Styling hinzufügen“Bearbeite src/styles/App.css:
* { box-sizing: border-box; margin: 0; padding: 0;}
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; background-color: #f5f5f5;}
.app-container { max-width: 600px; margin: 0 auto; padding: 20px; background-color: white; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); border-radius: 8px; margin-top: 40px;}
h1 { text-align: center; margin-bottom: 20px; color: #2c3e50;}
form { display: flex; margin-bottom: 20px;}
input { flex: 1; padding: 10px; font-size: 16px; border: 1px solid #ddd; border-radius: 4px 0 0 4px; outline: none;}
button { padding: 10px 15px; background-color: #3498db; color: white; border: none; cursor: pointer; transition: background-color 0.3s;}
form button { border-radius: 0 4px 4px 0;}
button:hover { background-color: #2980b9;}
.todo-list { margin-top: 20px;}
.todo-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 15px; margin-bottom: 10px; background-color: #f9f9f9; border-radius: 4px; transition: all 0.3s;}
.todo-text { cursor: pointer; flex: 1;}
.completed .todo-text { text-decoration: line-through; color: #888;}
.delete-button { background-color: #e74c3c; border-radius: 4px; padding: 5px 10px; font-size: 14px;}
.delete-button:hover { background-color: #c0392b;}
.empty-message { text-align: center; color: #7f8c8d; font-style: italic;}Schritt 6: App starten
Abschnitt betitelt „Schritt 6: App starten“npm run devÖffne http://localhost:5173 im Browser und teste deine App manuell.
Cypress Installation und Konfiguration
Abschnitt betitelt „Cypress Installation und Konfiguration“Schritt 7: Cypress installieren
Abschnitt betitelt „Schritt 7: Cypress installieren“npm install cypress --save-devSchritt 8: Cypress konfigurieren
Abschnitt betitelt „Schritt 8: Cypress konfigurieren“Füge in package.json ein Skript hinzu:
"scripts": { "dev": "vite", "build": "vite build", "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", "cypress:open": "cypress open", "cypress:run": "cypress run"}Initialisiere Cypress:
npx cypress openWähle E2E Testing im Cypress-Fenster und folge den Anweisungen zur Konfiguration.
Schritt 9: Cypress-Konfiguration anpassen
Abschnitt betitelt „Schritt 9: Cypress-Konfiguration anpassen“Bearbeite cypress.config.js:
import { defineConfig } from 'cypress'
export default defineConfig({ e2e: { baseUrl: 'http://localhost:5173', setupNodeEvents(on, config) { // implement node event listeners here }, },})End-to-End Tests mit Cypress
Abschnitt betitelt „End-to-End Tests mit Cypress“Schritt 10: Grundlegende Tests erstellen
Abschnitt betitelt „Schritt 10: Grundlegende Tests erstellen“Erstelle eine neue Datei cypress/e2e/todo.cy.js:
describe('Todo App', () => { beforeEach(() => { // Lokalen Speicher löschen und App besuchen cy.clearLocalStorage() cy.visit('/') })
it('sollte die Überschrift korrekt anzeigen', () => { cy.contains('h1', 'React Todo App') })
it('sollte anfangs eine leere Nachricht anzeigen', () => { cy.get('[data-cy=empty-message]').should('be.visible') .and('contain', 'Keine Todos vorhanden') })
it('sollte ein neues Todo hinzufügen können', () => { const todoText = 'React lernen'
cy.get('[data-cy=todo-input]').type(todoText) cy.get('[data-cy=add-button]').click()
cy.get('[data-cy=todo-item]').should('have.length', 1) cy.get('[data-cy=todo-text]').first().should('contain', todoText) cy.get('[data-cy=empty-message]').should('not.exist') })})Schritt 11: Erweiterte Tests hinzufügen
Abschnitt betitelt „Schritt 11: Erweiterte Tests hinzufügen“Erweitere cypress/e2e/todo.cy.js:
describe('Todo App', () => { // Bisherige Tests...
it('sollte mehrere Todos hinzufügen können', () => { const todos = ['Design erstellen', 'APIs implementieren', 'Tests schreiben']
todos.forEach(todo => { cy.get('[data-cy=todo-input]').type(todo) cy.get('[data-cy=add-button]').click() })
cy.get('[data-cy=todo-item]').should('have.length', todos.length)
todos.forEach((todo, index) => { cy.get('[data-cy=todo-text]').eq(index).should('contain', todo) }) })
it('sollte ein Todo als erledigt markieren können', () => { // Todo hinzufügen cy.get('[data-cy=todo-input]').type('Erledigt werden') cy.get('[data-cy=add-button]').click()
// Anfangs nicht markiert cy.get('[data-cy=todo-item]').should('not.have.class', 'completed')
// Todo anklicken, um es als erledigt zu markieren cy.get('[data-cy=todo-text]').click()
// Überprüfen, ob es als erledigt markiert wurde cy.get('[data-cy=todo-item]').should('have.class', 'completed')
// Erneut klicken, um die Markierung aufzuheben cy.get('[data-cy=todo-text]').click()
// Überprüfen, ob die Markierung entfernt wurde cy.get('[data-cy=todo-item]').should('not.have.class', 'completed') })
it('sollte ein Todo löschen können', () => { // Todo hinzufügen cy.get('[data-cy=todo-input]').type('Zu löschendes Todo') cy.get('[data-cy=add-button]').click()
// Überprüfen, ob es hinzugefügt wurde cy.get('[data-cy=todo-item]').should('have.length',1)
// Löschen-Button klicken cy.get('[data-cy=delete-button]').click()
// Überprüfen, ob es entfernt wurde cy.get('[data-cy=todo-item]').should('not.exist') cy.get('[data-cy=empty-message]').should('be.visible') })
it('sollte Todos im localStorage speichern', () => { // Todo hinzufügen const todoText = 'Persistentes Todo' cy.get('[data-cy=todo-input]').type(todoText) cy.get('[data-cy=add-button]').click()
// Seite neu laden cy.reload()
// Überprüfen, ob das Todo noch da ist cy.get('[data-cy=todo-item]').should('have.length', 1) cy.get('[data-cy=todo-text]').should('contain', todoText) })
it('sollte leere Todos nicht hinzufügen', () => { // Leer-Eingabe testen cy.get('[data-cy=add-button]').click() cy.get('[data-cy=todo-item]').should('not.exist')
// Nur Leerzeichen eingeben cy.get('[data-cy=todo-input]').type(' ') cy.get('[data-cy=add-button]').click() cy.get('[data-cy=todo-item]').should('not.exist') })})Schritt 12: Tests ausführen
Abschnitt betitelt „Schritt 12: Tests ausführen“# Zunächst: Starte deine Appnpm run dev
# In einem anderen Terminal: Öffne Cypressnpm run cypress:openIn der Cypress-Oberfläche:
- Wähle “E2E Testing”
- Wähle deinen Browser
- Klicke auf deine “todo.cy.js”-Datei
Alternativ für Headless-Ausführung:
npm run cypress:runTipps für effektives Testing
Abschnitt betitelt „Tipps für effektives Testing“1. Verwende data-cy Attribute
Abschnitt betitelt „1. Verwende data-cy Attribute“Nutze immer dedizierte Attribute wie data-cy für deine Tests. Diese ändern sich nicht, wenn du das Design anpasst.
// Gut<button data-cy="add-button">Hinzufügen</button>
// Weniger gut<button className="add-btn">Hinzufügen</button>2. Teste aus Benutzerperspektive
Abschnitt betitelt „2. Teste aus Benutzerperspektive“Teste, was der Benutzer tun würde, nicht interne Implementierungsdetails.
// Gutcy.get('[data-cy=todo-input]').type('Neues Todo')cy.get('[data-cy=add-button]').click()
// Weniger gutcy.window().its('app.todos').should('have.length', 1)3. Isolierte Tests
Abschnitt betitelt „3. Isolierte Tests“Jeder Test sollte unabhängig von anderen Tests sein. Nutze beforeEach für das Setup.
4. Custom Commands für wiederholte Aktionen
Abschnitt betitelt „4. Custom Commands für wiederholte Aktionen“Bei häufigen Aktionen, erstelle Custom Commands in cypress/support/commands.js:
// Custom Command zum Hinzufügen eines TodosCypress.Commands.add('addTodo', (text) => { cy.get('[data-cy=todo-input]').type(text) cy.get('[data-cy=add-button]').click()})
// Verwendung im Testcy.addTodo('Mein Todo')5. Automatisiere in CI/CD
Abschnitt betitelt „5. Automatisiere in CI/CD“Füge Cypress-Tests in deine CI/CD-Pipeline ein, z.B. mit GitHub Actions.
Mit dieser Kombination aus Vite für schnelles Entwickeln und Cypress für robustes Testing hast du einen modernen, effizienten Workflow für React-Anwendungen. Die Zeit, die du in Tests investierst, wird dir langfristig durch weniger Bugs und höhere Code-Qualität zurückgezahlt.
End-to-End-Tests mit Cypress bieten dir die Sicherheit, dass deine Anwendung aus Benutzerperspektive so funktioniert, wie du es erwartest. Es ist wie ein automatisierter QA-Tester, der 24/7 verfügbar ist!
Nächste Schritte:
- Lerne Component Testing mit Cypress
- Füge visuelle Regressionstests hinzu
- Implementiere weitere Features in deiner Todo-App und teste sie
Viel Erfolg bei deinem Projekt!