Unit-Tests sind wichtig, da sie kleine Teile des Codes überprüfen, um sicherzustellen, dass sie richtig funktionieren, und Fehler frühzeitig finden. Es ist wichtig, diese Tests durchzuführen, bevor eine App veröffentlicht wird. In diesem Leitfaden werden Unit-Tests mit Mocha und Chai behandelt.
Mocha ist ein funktionsreiches JavaScript-Testframework, das auf Node.js läuft und asynchrone Tests einfach und angenehm macht. Es bietet Funktionen, die in einer bestimmten Reihenfolge ausgeführt werden, Testergebnisse sammeln und genaue Berichte liefern.
Chai ist eine BDD/TDD-Assertionsbibliothek, die mit jedem JavaScript-Test-Framework verwendet werden kann. Es bietet mehrere Schnittstellen, sodass Entwickler den Behauptungsstil wählen können, den sie am bequemsten finden.
Lesbare und aussagekräftige Aussagen
Chai bietet verschiedene Assertionsstile und Syntaxoptionen, die gut mit Mocha funktionieren, sodass Sie den Stil auswählen können, der Ihren Anforderungen an Klarheit und Lesbarkeit entspricht.
Unterstützung für asynchrone Tests
Mocha handhabt asynchrone Tests problemlos, sodass Sie asynchronen Code in Node.js-Anwendungen testen können, ohne dass zusätzliche Bibliotheken oder komplexe Setups erforderlich sind.
Zuerst richten wir ein neues Node.js-Projekt ein und installieren die notwendigen Abhängigkeiten:
mkdir auth-api-testing cd auth-api-testing npm init -y # Install production dependencies npm install express jsonwebtoken mongoose bcryptjs dotenv # Install development dependencies npm install --save-dev mocha chai chai-http supertest nyc
Fügen Sie die folgenden Skripte zu Ihrem package.json hinzu:
{ "scripts": { "test": "NODE_ENV=test mocha --timeout 10000 --exit", "test:coverage": "nyc npm test" } }
Bevor wir uns mit dem Testen befassen, wollen wir uns mit der Anwendung befassen, die wir testen werden. Wir erstellen eine einfache, aber sichere Authentifizierungs-API mit den folgenden Funktionen.
src/ ├── models/ │ └── user.model.js # User schema and model ├── routes/ │ ├── auth.routes.js # Authentication routes │ └── user.routes.js # User management routes ├── middleware/ │ ├── auth.middleware.js # JWT verification middleware │ └── validate.js # Request validation middleware ├── controllers/ │ ├── auth.controller.js # Authentication logic │ └── user.controller.js # User management logic └── app.js # Express application setup
POST /api/auth/register - Registers new user - Accepts: { email, password, name } - Returns: { token, user } POST /api/auth/login - Authenticates existing user - Accepts: { email, password } - Returns: { token, user }
GET /api/users/profile - Gets current user profile - Requires: JWT Authentication - Returns: User object PUT /api/users/profile - Updates user profile - Requires: JWT Authentication - Accepts: { name, email } - Returns: Updated user object
Erstellen Sie eine .env.test-Datei für die testspezifische Konfiguration:
PORT=3001 MONGODB_URI=mongodb://localhost:27017/auth-api-test JWT_SECRET=your-test-secret-key
Lassen Sie uns unsere erste Testdatei test/auth.test.js erstellen
const chai = require('chai'); const chaiHttp = require('chai-http'); const app = require('../src/app'); const User = require('../src/models/user.model'); chai.use(chaiHttp); const expect = chai.expect; describe('Auth API Tests', () => { // Runs before all tests before(async () => { await User.deleteMany({}); }); // Runs after each test afterEach(async () => { await User.deleteMany({}); }); // Test suites will go here });
Mocha bietet mehrere Hooks für den Testaufbau und die Bereinigung:
before(): Wird einmal vor allen Tests ausgeführt
after(): Wird nach allen Tests einmal ausgeführt
beforeEach(): Wird vor jedem Test ausgeführt
afterEach(): Wird nach jedem Test ausgeführt
Tests werden mithilfe von beschreiben()-Blöcken zum Gruppieren verwandter Tests und it()-Blöcken für einzelne Testfälle organisiert:
mkdir auth-api-testing cd auth-api-testing npm init -y # Install production dependencies npm install express jsonwebtoken mongoose bcryptjs dotenv # Install development dependencies npm install --save-dev mocha chai chai-http supertest nyc
{ "scripts": { "test": "NODE_ENV=test mocha --timeout 10000 --exit", "test:coverage": "nyc npm test" } }
src/ ├── models/ │ └── user.model.js # User schema and model ├── routes/ │ ├── auth.routes.js # Authentication routes │ └── user.routes.js # User management routes ├── middleware/ │ ├── auth.middleware.js # JWT verification middleware │ └── validate.js # Request validation middleware ├── controllers/ │ ├── auth.controller.js # Authentication logic │ └── user.controller.js # User management logic └── app.js # Express application setup
POST /api/auth/register - Registers new user - Accepts: { email, password, name } - Returns: { token, user } POST /api/auth/login - Authenticates existing user - Accepts: { email, password } - Returns: { token, user }
GET /api/users/profile - Gets current user profile - Requires: JWT Authentication - Returns: User object PUT /api/users/profile - Updates user profile - Requires: JWT Authentication - Accepts: { name, email } - Returns: Updated user object
Jeder Test sollte für sich allein stehen und nicht von anderen Tests abhängig sein
Tests sollten in beliebiger Reihenfolge ausgeführt werden können
Verwenden Sie die Hooks „before“, „after“, „beforeEach“ und „afterEach“ für die ordnungsgemäße Einrichtung und Bereinigung
PORT=3001 MONGODB_URI=mongodb://localhost:27017/auth-api-test JWT_SECRET=your-test-secret-key
const chai = require('chai'); const chaiHttp = require('chai-http'); const app = require('../src/app'); const User = require('../src/models/user.model'); chai.use(chaiHttp); const expect = chai.expect; describe('Auth API Tests', () => { // Runs before all tests before(async () => { await User.deleteMany({}); }); // Runs after each test afterEach(async () => { await User.deleteMany({}); }); // Test suites will go here });
Testen Sie Randbedingungen, Fehlerszenarien, ungültige Eingaben und leere oder Nullwerte.
describe('Auth API Tests', () => { describe('POST /api/auth/register', () => { it('should register a new user successfully', async () => { // Test implementation }); it('should return error when email already exists', async () => { // Test implementation }); }); });
Testnamen sollten das getestete Szenario klar beschreiben, einer konsistenten Namenskonvention folgen und das erwartete Verhalten enthalten.
describe('POST /api/auth/register', () => { it('should register a new user successfully', async () => { const res = await chai .request(app) .post('/api/auth/register') .send({ email: 'test@example.com', password: 'Password123!', name: 'Test User' }); expect(res).to.have.status(201); expect(res.body).to.have.property('token'); expect(res.body).to.have.property('user'); expect(res.body.user).to.have.property('email', 'test@example.com'); }); it('should return 400 when email already exists', async () => { // First create a user await chai .request(app) .post('/api/auth/register') .send({ email: 'test@example.com', password: 'Password123!', name: 'Test User' }); // Try to create another user with same email const res = await chai .request(app) .post('/api/auth/register') .send({ email: 'test@example.com', password: 'Password123!', name: 'Test User 2' }); expect(res).to.have.status(400); expect(res.body).to.have.property('error'); }); });
describe('Protected Routes', () => { let token; let userId; beforeEach(async () => { // Create a test user and get token const res = await chai .request(app) .post('/api/auth/register') .send({ email: 'test@example.com', password: 'Password123!', name: 'Test User' }); token = res.body.token; userId = res.body.user._id; }); it('should get user profile with valid token', async () => { const res = await chai .request(app) .get('/api/users/profile') .set('Authorization', `Bearer ${token}`); expect(res).to.have.status(200); expect(res.body).to.have.property('email', 'test@example.com'); }); it('should return 401 with invalid token', async () => { const res = await chai .request(app) .get('/api/users/profile') .set('Authorization', 'Bearer invalid-token'); expect(res).to.have.status(401); }); });
const mongoose = require('mongoose'); before(async () => { await mongoose.connect(process.env.MONGODB_URI_TEST); }); after(async () => { await mongoose.connection.dropDatabase(); await mongoose.connection.close(); });
describe('User CRUD Operations', () => { it('should update user profile', async () => { const res = await chai .request(app) .put(`/api/users/${userId}`) .set('Authorization', `Bearer ${token}`) .send({ name: 'Updated Name' }); expect(res).to.have.status(200); expect(res.body).to.have.property('name', 'Updated Name'); }); it('should delete user account', async () => { const res = await chai .request(app) .delete(`/api/users/${userId}`) .set('Authorization', `Bearer ${token}`); expect(res).to.have.status(200); // Verify user is deleted const user = await User.findById(userId); expect(user).to.be.null; }); });
Das Schreiben manueller Testfälle für Mokka mit Chai ist zwar effektiv, birgt jedoch oft mehrere Herausforderungen:
Zeitaufwändig: Die manuelle Erstellung detaillierter Testsuiten kann viel Zeit in Anspruch nehmen, insbesondere bei großen Codebasen.
Schwierig zu warten: Wenn sich Ihre Anwendung ändert, wird die Aktualisierung und Wartung manueller Tests komplexer und fehleranfälliger.
Inkonsistente Abdeckung: Entwickler konzentrieren sich möglicherweise auf die Hauptpfade, fehlende Randfälle oder Fehlerszenarien, die Fehler in der Produktion verursachen könnten.
Fähigkeitsabhängig: Die Qualität und Wirksamkeit manueller Tests hängen stark von den Testfähigkeiten des Entwicklers und seiner Vertrautheit mit der Codebasis ab.
Repetitiv und mühsam: Das Schreiben ähnlicher Teststrukturen für mehrere Komponenten oder Funktionen kann langweilig sein und möglicherweise zu weniger Liebe zum Detail führen.
Verzögertes Feedback: Die zum Schreiben manueller Tests benötigte Zeit kann die Entwicklung verlangsamen und wichtige Rückmeldungen zur Codequalität und -funktionalität verzögern.
Um diese Probleme anzugehen, hat Keploy ein ut-gen eingeführt, das KI nutzt, um den Testprozess zu automatisieren und zu verbessern. Dies ist die erste Implementierung des Meta LLM-Forschungspapiers, das Codesemantik versteht und aussagekräftige Unit-Tests erstellt.
Ziel ist es, die Unit-Test-Generierung (UTG) durch die schnelle Erstellung gründlicher Unit-Tests zu automatisieren. Dadurch wird der Bedarf an repetitiver manueller Arbeit reduziert, Randfälle verbessert, indem die Tests erweitert werden, um komplexere Szenarien abzudecken, die häufig manuell übersehen werden, und die Testabdeckung erhöht, um eine vollständige Abdeckung sicherzustellen, wenn die Codebasis wächst.
%[https://marketplace.visualstudio.com/items?itemName=Keploy.keployio]
Automatisierte Unit-Test-Generierung (UTG): Generieren Sie schnell umfassende Unit-Tests und reduzieren Sie redundanten manuellen Aufwand.
Randfälle verbessern: Erweitern und verbessern Sie den Testumfang, um komplexere Szenarien abzudecken, die häufig manuell übersehen werden.
Erhöhung der Testabdeckung: Mit zunehmender Codebasis wird es möglich, eine umfassende Abdeckung sicherzustellen.
Zusammenfassend lässt sich sagen, dass die Beherrschung des Node.js-Backend-Tests mit Mocha und Chai wichtig für Entwickler ist, die möchten, dass ihre Anwendungen zuverlässig und stark sind. Durch die Verwendung des Test-Frameworks von Mocha und der Clear-Assertion-Bibliothek von Chai können Entwickler detaillierte Testsuiten erstellen, die viele Teile ihres Codes abdecken, von einfachen Unit-Tests bis hin zu komplexen asynchronen Vorgängen. Das Befolgen von Best Practices wie die Fokussierung der Tests, die Verwendung klarer Namen und der korrekte Umgang mit Versprechen kann Ihren Testprozess erheblich verbessern. Durch den Einsatz dieser Tools und Techniken in Ihrem Entwicklungsworkflow können Sie Fehler frühzeitig finden, die Codequalität verbessern und sicherere und effizientere Anwendungen bereitstellen.
Obwohl es sich bei beiden um Testwerkzeuge handelt, dienen sie unterschiedlichen Zwecken. Mocha ist ein Test-Framework, das die Struktur zum Organisieren und Ausführen von Tests bereitstellt (mithilfe der Blöcke „beschreiben()“ und „it()“). Chai ist eine Assertionsbibliothek, die Funktionen zum Überprüfen von Ergebnissen bereitstellt (wie „expect()“, „should“ und „assertion“). Während Sie Mokka ohne Chai verwenden können, erhalten Sie durch die gemeinsame Verwendung eine umfassendere und ausdrucksstärkere Testlösung.
Mocha bietet mehrere Lebenszyklus-Hooks für die Verwaltung von Testdaten:
before(): Vor allen Tests einmal ausführen
beforeEach(): Vor jedem Test ausführen
afterEach(): Nach jedem Test ausführen
after(): Nach allen Tests einmal ausführen
Beispiel:
mkdir auth-api-testing cd auth-api-testing npm init -y # Install production dependencies npm install express jsonwebtoken mongoose bcryptjs dotenv # Install development dependencies npm install --save-dev mocha chai chai-http supertest nyc
Gruppieren Sie verwandte Tests mithilfe von beschreiben()-Blöcken
Verwenden Sie beschreibende Testnamen, die das erwartete Verhalten klar zum Ausdruck bringen
Folgen Sie bei jedem Test dem AAA-Muster (Arrange-Act-Assert)
Tests atomar und unabhängig halten
Testdateien so organisieren, dass sie Ihre Quellcodestruktur widerspiegeln
Beispiel:
{ "scripts": { "test": "NODE_ENV=test mocha --timeout 10000 --exit", "test:coverage": "nyc npm test" } }
„before“ wird einmal vor allen Tests ausgeführt, „beforeEach“ wird vor jedem einzelnen Test ausgeführt, „afterEach“ wird nach jedem einzelnen Test ausgeführt und „after“ wird einmal ausgeführt, nachdem alle Tests abgeschlossen sind. Diese sind nützlich zum Einrichten und Bereinigen von Testdaten.
Sie können in Ihren Tests Async/Await- oder Return-Promises verwenden. Fügen Sie einfach „async“ vor Ihrer Testfunktion hinzu und verwenden Sie „await“, wenn Sie asynchrone Vorgänge aufrufen. Stellen Sie sicher, dass Sie für längere Vorgänge geeignete Zeitüberschreitungen festlegen.
Das obige ist der detaillierte Inhalt vonUnit-Tests für NodeJS mit Mocha und Chai. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!