Node.js und Logging – Teil 5: Anpassung der Unit-Tests
Unit-Tests sollen Sicherheit geben – Logausgaben in den Testzusammenfassungen stören gewaltig. In diesem Artikel räumen wir auf: Wir setzen Umgebungsvariablen gezielt im Testkontext ein, schalten damit störende Logs ab und nutzen dafür unseren zentralen LogService. Los geht's!
Im heutigen Artikel werden wir die notwendigen Anpassungen für reibungsfreie Unit-Tests vornehmen. Das umfasst folgende Aspekte:
- Umgebungsvariablen bei Unit-Test
- Anpassung des LogServices
- Aktualisierung der Codebasis
Bevor wir loslegen, schau dir die bisherigen Artikel dieser Serie an. Du findest die Links im Überblick zur Artikelserie.
1. Umgebungsvariablen für Unit-Tests
Dank unserer umsichtigen Programmierung haben wir bei der Unit-Test-Ausführung keinen Fehler im EnvVarServices festgestellt. Der Grund ist, dass mit der withDevDefault-Implementierung für alle bisherigen Umgebungsvariablen ein Standardwert festgelegt wird.
Sobald wir eine Mandatory-Variable ohne Standardwert haben, wird ein Fehler auftreten. Um darauf vorbereitet zu sein, legen wir im Testkontext Werte an einem zentralen Ort fest. Dazu erstellen wir eine Datei namens setupTestEnvVars.ts im Ordner /tests/helper.
Für unseren aktuellen Stand gilt es, dort die Umgebungsvariable NODE_ENV festzulegen. Dies nutzen wir weiter unten, um die Ausgabe der Logs während der Unit-Tests zu unterbinden.
process.env.NODE_ENV = 'test'
Damit die neue Datei /tests/helper/setupEnvVars.ts im Testkontext berücksichtigt wird, müssen wir die Vitest-Konfiguration erweitern. Hierzu listen wir die neue Datei in SetupFiles der Vitest-Konfiguration vitest.config.js auf:
export default {
test: {
// ...
setupFiles: [
'tests/helper/setupTestEnvVars.ts',
// ...
],
},
// ...
}
2. Anpassung des LogServices
Bei der Erstellung des LogServices geben wir ein Flag namens isSilent mit, das wir verwenden, um das Winston-Logging zu deaktivieren. Die Logik zur Bestimmung des Wertes dieses Flags verknüpfen wir mit der Umgebungsvariable NODE_ENV: Ist deren Wert test, setzen wir isSilent auf true. Dank unserer Anpassung in Punkt 1 funktioniert der Ansatz.
// We add the flag to the args
type LogServiceArgs = {
logLevel: LogLevel
logFormat: LogFormat
isSilent: boolean
}
// We set the flag based on the NODE_ENV
const LogService = ServiceFactory({
logLevel: EnvVarService.logging.logLevel,
logFormat: EnvVarService.logging.logFormat,
isSilent: EnvVarService.app.nodeEnv === NodeEnvironments.TEST
})
// We apply the flag in the logger creation
const logger = winston.createLogger({
silent: args.isSilent,
// ...
})
Damit die Log-Ausgaben vollständig verschwinden, gilt es noch, die bisherigen Konsolen-Log-Ausgaben in LogService-Ausgaben umzustellen.
3. Aktualisierung der Codebasis
Innerhalb unseres /src-Verzeichnisses suchen wir nach der Zeichenkette console.log() und ersetzen sie durch LogService.debug(). Zusätzlich ist bei den betroffenen Dateien an das Import-Statement import LogService from '@/services/LogService' zu denken.
Fazit
Mithilfe der Umgebungsvariablen für den LogLevel können wir den Detailgrad der Logs einfach steuern, ohne im Fehlerfall zusätzliche Konsolen-Logausgaben hinzuzufügen. Deshalb ist es wichtig bei der Entwicklung bereits darauf zu achten, welche Ausgaben welchen LogLevel erhalten.
Kleiner Exkurs
Eine nützliche Angewohntheit ist, die Applikation in den drei LogLeveln debug, verbose und info laufen zu lassen. Schau dir die Ausgabe an und überlege dir, wie geschwätzig die Applikation sein soll.
- Bei
infowillst du nur relevante Fakten sehen, um nachzuvollziehen, dass etwas passiert ist. - Bei
verbosedarf der Detailgrad höher sein, jedoch soll der eigentliche Ablauf nicht in einer Flut an Informationen untergehen. - Schlussendlich
debug: Hier muss alles zu finden sein, damit du exakt verstehst, was mit welchen Parametern aufgerufen wurde und wieso die Ergebnisse entstanden sind.
Oft wird ins Feld geführt, dass wir die Applikation stattdessen debuggen können. Mit Breakpoints tief einsteigen. Absolut richtig. Das ist ein weiterer Ansatz. Nur ist das meist nicht nötig, wenn ich alle Informationen aus meinen Logs habe. Damit schreibe ich einen Unit-Test, der das Fehlverhalten nachstellt. Ein solcher Test ist bereits 50 % der Lösung. Im Rahmen der Testausführung kann ich mithilfe von Debugging begrenzt den Fehler analysieren.
Zurück zum Thema
Dieser Ansatz – so viele Logs in den Code zu schreiben – führt dazu, dass bei den Unit-Tests sämtliche Log-Ausgaben in den Testergebnissen enthalten sind. Dies empfinde ich als unübersichtlich in den Ausgaben für das Testing, weswegen wir heute die Maßnahme ergriffen haben, um es zu verhindern. Ergänzend haben wir einen Ort definiert, um künftig Angaben zu zwingend erforderlichen Umgebungsvariablen ohne Standardwert im Unit-Test-Kontext zu speichern.
Im nächsten Artikel gehen wir wieder in den LogService hinein und ergänzen dessen Funktionalität um die Filterung sensibler Informationen, etwa bei der Ausgabe von E-Mail-Adressen.