Angular en Vitest: Unit & Integration Tests
Auteur: Danny Holstein
In april 2023 heeft het Angular development team de status van de standaard Karma test-runner op deprecated gezet. Karma wordt vaak in combinatie gebruikt met Jasmine dat het testing framework is en structuur biedt aan unit tests. Nog voor de release van Angular 21 evalueerde het Angular development team drie potentiële vervangers voor Karma, namelijk: Web Test Runner, Jest en Vitest. In Angular 21 is er een besluit genomen waardoor Vitest de nieuwe standaard test-runner is. In dit nieuwe blogbericht wordt Vitest en de toepassing hiervan in verschillende Angular versies nader besproken.
Uiteindelijke keuze voor Vitest
Zoals in de inleiding vermeld, was het Angular development team mogelijke vervangers voor de Karma test-runner aan het onderzoeken. Karma is ongeveer 10 jaar geleden ontwikkeld en is een op browser-gebaseerde test runner die tests uitvoert in echte browsers. Een van de kandidaten was de Web Test Runner die net zoals Karma in de browser unit tests uitvoert. De Web Test Runner is door de browser overhead trager en moeilijker in te stellen. Een tweede kandidaat was Jest, maar dit kan eveneens lastig zijn om in te stellen en heeft issues met Angular.
Doordat Vite in Angular 18 al de standaard development-server is geworden, werd het mogelijk om Vitest te gebruiken in Angular projecten en werden de technische barrières aanzienlijk verminderd. Vitest schakelt Vite’s snellere build system in en dit leidt tot betere prestaties. Vitest is verder populair binnen de JavaScript community (Vitest was al aangenomen als test-runner in Vue, React en Svelte) en heeft een vergelijkbare syntax met Karma en Jest. Verder is Vitest redelijk makkelijk onder de knie te krijgen en is de leercurve minder steil wanneer een developer al bekend is met Karma en Jasmine. Al snel werd Vitest een voorkeurs-kandidaat en een logische vervanger voor de Karma test-runner.
Vitest + Angular versies 18 tot en met 20
De Vitest test-runner is vanaf Angular 18 te gebruiken. Net zoals Jasmine kent Vitest in de unit tests de describe- en it-blokken. In de Angular versies 18 tot en met 20 is Vitest het makkelijkste te installeren door een plug-in te gebruiken met AnalogJS. Deze plug-in neemt het handmatig instellen van Vitest uit handen.
In de praktijk bleek dat het handiger is om een langer commando te gebruiken in de terminal waarbij ook de dependencies worden geïnstalleerd. Vervolgens is Vitest verder te configureren, waarbij het nodige bestand vite.config.mts automatisch wordt aangemaakt. Het resultaat is dat er in de devDependencies sectie van package.json verschillende pakketten zijn toegevoegd. Zie de afbeelding hieronder:
Het bestand vite.config.mts bevat de configuratie van Vitest en zal zich in de root van het project bevinden. Er staat ook in welke environment er wordt gebruikt, namelijk jsdom dat de standaard is, maar dit is ook eventueel te veranderen in happy-dom voor nog snellere prestaties. Alleen dit laatste moet dan wel apart worden ingesteld.
In de src-folder van een Angular project wordt de test environment verder ingesteld in test-setup.ts voor Vitest. De AnalogJS plug-in past verder angular.json aan en ook wordt het bestand tsconfig.spec.json geüpdatet. Om Vitest tests uit te voeren (met het commando npm run test) zijn er wel handmatige veranderingen nodig in package.json. Zie de afbeelding hieronder:
Een ander voordeel van Vitest is dat er code coverage is te zien. Dit geeft aan welke code er bestreken wordt door middel van tests. De code coverage is in de terminal te zien met het commando: npm run coverage – dit commando zal eerst alle unit- en integration tests uitvoeren, waarna de code coverage in een kleurrijke en intuïtieve tabel wordt getoond. In de laatste kolom wordt getoond welke regels code er nog niet bestreken zijn door tests. Er is ook in een oogopslag te zien in hoeverre alle paden van bijvoorbeeld een functie worden getest. Zie de afbeelding hieronder:
Voor Angular versie 18 tot en met 20 biedt Vitest al de nodige voordelen. In bepaalde situaties kunnen zich echter wel compatibiliteitsproblemen voordoen met ESM/CommonJS die afkomstig zijn uit andere libraries. De in deze paragraaf getoonde implementatie was nog niet officieel ondersteund door Angular en berust op het pakket van AnalogJS. Het gevolg hiervan is dat er problemen kunnen zijn met dependencies van derde partijen. Zoals hierboven aangetoond neemt de plug-in van AnalogJS al veel werk uit handen om zaken juist in te stellen. Uit de praktijk bleek wel dat bepaalde tests (in het bijzonder die gebruik maken van fakeAsync en tick) herschreven dienden te worden. Deze problemen en nadelen zullen in de toekomst verder worden verholpen, aangezien Vitest sinds Angular 21 wel officiële ondersteuning heeft van het Angular development team.
Vitest + Angular 21 in bestaande projecten
Een andere overweging van het Angular development team om Vitest officieel te ondersteunen was dat veel developers positieve ervaringen en snellere tests rapporteerden. Niet alleen de snelheid van Vitest, maar ook moderne features (zoals snapshot testing waarbij ‘momentopnames’ worden gemaakt en dit later vergeleken kan worden, plus uitstekende ondersteuning voor TypeScript) zijn een overweging geweest voor officiële ondersteuning. Met Vitest wordt browser overhead vermeden doordat Vitest ook is uit te voeren in Node.js (met jsdom of happydom). Vitest bleek een moderne en snelle alternatieve test-runner te zijn ten opzichte van zijn rivalen.
Er is moeite in gestopt door het Angular Development Team om de configuratie nog verder te versimpelen. Maar met de komst van Angular 21 is er tegelijkertijd een ‘technisch gat’ ontstaan. In grote lijnen houdt dit het volgende in:
1. Angular 18 tot en met 20: Vitest wordt direct gebruikt door de niet-officiële AnalogJS en nx-pakketten.
2. Angular 21: Vitest is de officiële test-runner onder de motorkap.
Opmerkelijke veranderingen hebben zich voorgedaan in zowel angular.json als package.json. Door de officiële ondersteuning van Vitest wordt in de test-section in angular.json nu verwezen naar @angular/build:unit-tests – en het is optioneel om options te gebruiken. Andere veranderingen zijn er in package.json – niet alleen in de scripts-section om de Vitest test-runner uit te voeren, maar het is ook opvallend dat het aantal benodigde pakketten in de devDependencies section aanzienlijk minder is.
In de praktijk bleek dat watch-mode (die wacht op veranderingen in de code) van Vitest in Angular 21 op dit moment niet vlekkeloos verloopt en hierdoor heb ik de optie --no-watch in package.json toegevoegd. Deze laatste optie zorgt er echter wel voor dat de tests maar een enkele keer wordt uitgevoerd, maar zorgde er ook voor dat de tests op een nette manier worden afgesloten en eventueel via dezelfde terminal opnieuw kunnen worden uitgevoerd. Zie de afbeelding hieronder:
In de praktijk bleek ook dat oude configuratiebestanden met de nieuwe stijl storingen veroorzaakten. In Angular 21 wordt op de achtergrond al automatisch initTestEnvironment aangeroepen die de testomgeving zal initialiseren, maar dit wordt ook aangeroepen in het oude configuratiebestand src/test-setup.ts met als resultaat dat Vitest het commando ng test niet zal begrijpen. De oplossing is gelukkig wel simpel, want met de nieuwe stijl kan het bestand src/test-setup.ts worden verwijderd.
In Angular 21 bestaat de configuratie hoofdzakelijk uit de 3 bestanden: angular.json, vitest.config.ts (soms vitest.config.mts) en tsconfig.spec.json. Alsnog deden zich er in de praktijk allerlei andere problemen voor en hierbij bleek een minimaal Vitest configuratiebestand erg behulpzaam. Andere stoorzenders kunnen oude verwijzingen zijn naar Karma/Jasmine en oude configuratiebestanden.
Naar alle waarschijnlijkheid zijn er nieuwe bugs en andere compatibiliteitsproblemen met Vitest in Angular 21 applicaties. Een voorbeeld daarvan is de error die ik kreeg: ”Cannot find name ‘Disposable’” dat veroorzaak wordt door een Vitest dependency in het pakket @vitest/spy. De foutmelding hield in dat er een bepaalde definitie simpelweg ontbreekt – zie afbeelding hieronder:
Na het updaten van dit bovenstaande pakket (met het commando npm update @vitest/spy) bleef echter het probleem bestaan. Voor nu wordt er om dit probleem heen gewerkt in bepaalde projecten, door zelf een declaration file te maken en dit te plaatsen in de folder van het project: src/types/disposable.d.ts
Na de nodige verbeteringen, ziet het bestand vitest.config.ts er eveneens anders uit in Angular 21. Het coverabe-block is ook toegevoegd, maar een voorwaarde is wel dat het pakket @vitest/coverage-v8 is geïnstalleerd.
Een voordeel van Angular 21 is dat er een schematic is – die in de toekomst verder zal worden uitgewerkt – om de Jasmine tests te converteren in Vitest tests. Het commando voor deze schematic is: ng g @schematics/angular:refactor-jasmine-vitest
Maar ook in Angular 21 moeten tests die gebruik maken van fakeAsync en tick worden herschreven. Dit is te bereiken door in de tests gebruik te maken van Vitest equivalenten: vi.useFakeTimers (wordt opgenomen in het TestBed) en vi.advanceTimersByTime (als vervanging voor tick binnen de verschillende tests). Zie de voorbeeldcode hieronder:
Tot slot
Met de komst van Angular 18 en de Vite development-server is er veel veranderd en is Vitest te gebruiken binnen Angular projecten. Tot en met Angular versie 20 is Vitest te installeren met de AnalogJS plug-in dat niet-officieel is.
Met de komst van Angular 21 is er wel officiële ondersteuning en zal naar alle waarschijnlijkheid het ‘technische gat’ worden gedicht (dat in dit blogbericht is besproken) en andere compatibiliteitsproblemen worden verholpen. In ieder geval is het Angular Development Team door de officiële steun aan Vitest een juiste weg ingeslagen.