So starten Sie den Unit-Test Ihres JavaScript-Codes

Wir alle wissen, dass wir Unit-Tests schreiben sollten. Es ist jedoch schwierig zu wissen, wo man anfangen soll und wie viel Zeit für Tests im Vergleich zur tatsächlichen Implementierung aufgewendet werden muss. Also, wo soll ich anfangen? Und geht es nur darum, Code zu testen, oder haben Unit-Tests andere Vorteile?

In diesem Artikel werde ich die verschiedenen Arten von Tests erläutern und erläutern, welche Vorteile Unit-Tests für Entwicklungsteams haben. Ich werde Jest vorstellen - ein JavaScript-Testframework.

Verschiedene Arten von Tests

Bevor wir uns mit den Einzelheiten der Komponententests befassen, möchte ich einen kurzen Überblick über die verschiedenen Arten von Tests geben. Es gibt oft einige Verwirrung um sie und ich bin nicht überrascht. Manchmal ist die Linie zwischen ihnen ziemlich dünn.

Unit-Tests

Unit-Tests testen nur einen Teil Ihrer Implementierung. Eine Einheit. Keine Abhängigkeiten oder Integrationen, keine Framework-Besonderheiten. Sie sind wie eine Methode, die einen Link in einer bestimmten Sprache zurückgibt:

export function getAboutUsLink(language){ switch (language.toLowerCase()){ case englishCode.toLowerCase(): return '/about-us'; case spanishCode.toLowerCase(): return '/acerca-de'; } return ''; }

Integrationstests

Irgendwann kommuniziert Ihr Code mit einer Datenbank, einem Dateisystem oder einem anderen Dritten. Es könnte sogar ein anderes Modul in Ihrer App sein.

Diese Implementierung sollte durch Integrationstests getestet werden. Sie sind in der Regel komplizierter eingerichtet und umfassen das Vorbereiten von Testumgebungen, das Initialisieren von Abhängigkeiten usw.

Funktionsprüfungen

Unit-Tests und Integrationstests geben Ihnen das Vertrauen, dass Ihre App funktioniert. Funktionstests betrachten die App aus Sicht des Benutzers und testen, ob das System wie erwartet funktioniert.

In der obigen Abbildung sehen Sie, dass Komponententests die große Basis der Testsuite Ihrer Anwendung bilden. Normalerweise sind sie klein, es gibt viele von ihnen und sie werden automatisch ausgeführt.

Lassen Sie uns nun etwas detaillierter auf Unit-Tests eingehen.

Warum sollte ich Schreibeinheitentests stören?

Wenn ich Entwickler frage, ob sie Tests für ihre Anwendung geschrieben haben, sagen sie mir immer: "Ich hatte keine Zeit für sie" oder "Ich brauche sie nicht, ich weiß, dass es funktioniert."

Also lächle ich höflich und sage ihnen, was ich dir sagen möchte. Bei Unit-Tests geht es nicht nur um Tests. Sie helfen Ihnen auch auf andere Weise, sodass Sie:

Seien Sie sicher, dass Ihr Code funktioniert. Wann haben Sie das letzte Mal eine Codeänderung vorgenommen, Ihr Build ist fehlgeschlagen und die Hälfte Ihrer App funktioniert nicht mehr? Meins war letzte Woche.

Aber das ist immer noch in Ordnung. Das eigentliche Problem besteht darin, dass der Build erfolgreich ist, die Änderung bereitgestellt wird und Ihre App instabil wird.

In diesem Fall verlieren Sie das Vertrauen in Ihren Code und beten schließlich nur dafür, dass die App funktioniert. Mithilfe von Unit-Tests können Sie Probleme viel früher erkennen und Vertrauen gewinnen.

Treffen Sie bessere architektonische Entscheidungen. Codeänderungen, aber einige Entscheidungen über Plattform, Module, Struktur und andere müssen in den frühen Phasen eines Projekts getroffen werden.

Wenn Sie gleich zu Beginn über Unit-Tests nachdenken, können Sie Ihren Code besser strukturieren und die richtigen Probleme lösen. Sie werden nicht versucht sein, einzelnen Codeblöcken mehrere Verantwortlichkeiten zuzuweisen, da dies ein Albtraum für Unit-Tests wäre.

Präzise Funktionalität vor dem Codieren. Sie schreiben die Signatur der Methode und beginnen sofort mit der Implementierung. Oh, aber was soll passieren, wenn ein Parameter null ist? Was ist, wenn der Wert außerhalb des erwarteten Bereichs liegt oder zu viele Zeichen enthält? Wirfst du eine Ausnahme oder gibst null zurück?

Unit-Tests helfen Ihnen, all diese Fälle zu entdecken. Wenn Sie sich die Fragen noch einmal ansehen, werden Sie feststellen, dass genau dies Ihre Unit-Testfälle definiert.

Ich bin sicher, dass das Schreiben von Unit-Tests noch viele weitere Vorteile bietet. Dies sind nur diejenigen, an die ich mich aus meiner Erfahrung erinnere. Die, die ich auf die harte Tour gelernt habe.

So schreiben Sie Ihren ersten JavaScript-Unit-Test

Aber kehren wir zu JavaScript zurück. Wir beginnen mit Jest, einem JavaScript-Testframework. Es ist ein Tool, das automatische Unit-Tests ermöglicht, Codeabdeckung bietet und das einfache Verspotten von Objekten ermöglicht. Jest hat auch eine Erweiterung für Visual Studio Code hier verfügbar.

Es gibt auch andere Frameworks. Wenn Sie interessiert sind, können Sie diese in diesem Artikel überprüfen.

npm i jest --save-dev 

Verwenden getAboutUsLinkwir die zuvor erwähnte Methode als Implementierung, die wir testen möchten:

const englishCode = "en-US"; const spanishCode = "es-ES"; function getAboutUsLink(language){ switch (language.toLowerCase()){ case englishCode.toLowerCase(): return '/about-us'; case spanishCode.toLowerCase(): return '/acerca-de'; } return ''; } module.exports = getAboutUsLink; 

Ich habe das in die index.jsDatei aufgenommen. Wir können Tests in dieselbe Datei schreiben, aber es empfiehlt sich, Komponententests in eine dedizierte Datei zu unterteilen.

Die gebräuchlichen Namensmuster umfassen {filename}.test.jsund {filename}.spec.js. Ich habe das erste benutzt index.test.js:

const getAboutUsLink = require("./index"); test("Returns about-us for english language", () => { expect(getAboutUsLink("en-US")).toBe("/about-us"); }); 

Zuerst müssen wir die Funktion importieren, die wir testen möchten. Jeder Test wird als Aufruf der testFunktion definiert. Der erste Parameter ist der Name des Tests als Referenz. Die andere ist eine Pfeilfunktion, bei der wir die Funktion aufrufen, die wir testen möchten, und angeben, welches Ergebnis wir erwarten. ich

In diesem Fall rufen wir getAboutUsLinkfunction mit en-USals Sprachparameter auf. Wir erwarten das Ergebnis /about-us.

Jetzt können wir die Jest-CLI global installieren und den Test ausführen:

npm i jest-cli -g jest 

Wenn Sie einen Konfigurationsfehler sehen, stellen Sie sicher, dass Ihre package.jsonDatei vorhanden ist. Falls Sie dies nicht tun, generieren Sie eine mit npm init.

Sie sollten so etwas sehen:

 PASS ./index.test.js √ Returns about-us for english language (4ms) console.log index.js:15 /about-us Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 2.389s 

Great job! This was the first simple JavaScript unit test from start to end. If you installed the Visual Studio Code extension, it will run tests automatically once you save a file. Let's try it by extending the test with this line:

expect(getAboutUsLink("cs-CZ")).toBe("/o-nas"); 

Once you save the file, Jest will inform you that the test failed. That helps you discover potential issues even before committing your changes.

Testing Advanced Functionality and Mocking Services

In real life, the language codes for the getAboutUsLink method would not be constants in the same file. Their value is typically used throughout the project so they would be defined in their own module and imported into all functions that use them.

import { englishCode, spanishCode } from './LanguageCodes' 

You can import these constants into the test the same way. But the situation will get more complicated if you're working with objects instead of simple constants. Take a look at this method:

import { UserStore } from './UserStore' function getUserDisplayName(){ const user = UserStore.getUser(userId); return `${user.LastName}, ${user.FirstName}`; } 

This method uses imported UserStore:

class User { getUser(userId){ // logic to get data from a database } setUser(user){ // logic to store data in a database } } let UserStore = new User(); export { UserStore } 

In order to properly unit test this method, we need to mock UserStore. A mock is a substitute for the original object. It allows us to separate dependencies and real data from the tested method's implementation just like dummies help with crash tests of cars instead of real people.

If we didn't use the mock, we'd be testing both this function and the store. That would be an integration test and we would likely need to mock the used database.

Mocking a Service

To mock objects, you can either provide a mocking function or a manual mock. I will focus on the latter as I have a plain and simple use-case. But feel free to check out other mocking possibilities Jest provides.

jest.mock('./UserStore', () => ({     UserStore: ({         getUser: jest.fn().mockImplementation(arg => ({             FirstName: 'Ondrej',             LastName: 'Polesny'         })), setUser: jest.fn()     }) })); 

First, we need to specify what are we mocking - the ./UserStore module. Next, we need to return the mock that contains all exported objects from that module.

In this sample, it's only the User object named UserStore with the function getUser. But with real implementations, the mock may be much longer. Any functions you don't really care about in the scope of unit testing can be easily mocked with jest.fn().

The unit test for the getUserDisplayName function is similar to the one we created before:

test("Returns display name", () => {     expect(getUserDisplayName(1)).toBe("Polesny, Ondrej"); }) 

As soon as I save the file, Jest tells me I have 2 passing tests. If you're executing tests manually, do so now and make sure you see the same result.

Code Coverage Report

Now that we know how to test JavaScript code, it's good to cover as much code as possible with tests. And that is hard to do. In the end, we're just people. We want to get our tasks done and unit tests usually yield an unwanted workload that we tend to overlook. Code coverage is a tool that helps us fight that.

Code coverage will tell you how big a portion of your code is covered by unit tests. Take for example my first unit test checking the getAboutUsLink function:

test("Returns about-us for english language", () => {    expect(getAboutUsLink("en-US")).toBe("/about-us"); }); 

It checks the English link, but the Spanish version stays untested. The code coverage is 50%. The other unit test is checking the getDisplayName function thoroughly and its code coverage is 100%. Together, the total code coverage is 67%. We had 3 use cases to test, but our tests only cover 2 of them.

To see the code coverage report, type the following command into the terminal:

jest --coverage 

Or, if you're using Visual Studio Code with the Jest extension, you can run the command (CTRL+SHIFT+P) Jest: Toggle Coverage Overlay. It will show you right in the implementation which lines of code are not covered with tests.

By running the coverage check, Jest will also create an HTML report. Find it in your project folder under coverage/lcov-report/index.html.

Now, I don't have to mention that you should strive for 100% code coverage, right? :-)

Summary

In this article, I showed you how to start with unit testing in JavaScript. While it's nice to have your code coverage shine at 100% in the report, in reality, it's not always possible to (meaningfully) get there. The goal is to let unit tests help you maintain your code and ensure it always works as intended. They enable you to:

  • clearly define implementation requirements,
  • better design your code and separate concerns,
  • discover issues you may introduce with your newer commits,
  • and give you confidence that your code works.

The best place to start is the Getting started page in the Jest documentation so you can try out these practices for yourself.

Do you have your own experience with testing code? I'd love to hear it, let me know on Twitter or join one of my Twitch streams.