CONNECTED Conference 2023 - Aufzeichnungen jetzt hier verfügbar +++                     

Suche

über alle News und Events

 

Alle News

 

Vom 5. bis 7. Juni findet in London wieder die...

Weiterlesen

Live Coding on stage in Hamburg - exklusiv für...

Weiterlesen

Um Azure Ressourcen von DevOps aus zu deployen,...

Weiterlesen

In diesem Artikel wird ein beispielhafter Aufbau...

Weiterlesen

Die QUIBIQ Gruppe wurde am 9. März 2023 von dem...

Weiterlesen

Der Schnelleinstieg in die Welt der Tests für den...

Weiterlesen

Wenn man eine, im Azure Portal, entwickelte Logic...

Weiterlesen

WinSCP für BizTalk wird für den SFTP-Adapter...

Weiterlesen

Mit diesem Artikel wird ein klares Verständnis von...

Weiterlesen

In einer Logic App werden häufig Variablen...

Weiterlesen

How-to: Angular 12. Unit Tests

Mit Unit-Tests können wir sicherstellen, dass einzelne Teile der Anwendung genau so funktionieren, wie wir es erwarten...

Ferdinand Malcher und Johannes Hoppe schreiben z.B. in ihrem Buch Angular: Grundlagen, fortgeschrittene Themen und Best Practices: „…beim Testing wollen wir beweisen, dass unsere Software die an sie gestellten Anforderungen fehlerfrei erfüllt. Weiterhin stellen Tests eine Dokumentation der fachlichen oder technischen Anforderungen dar. Zudem erhöhen Tests insgesamt die Qualität unserer Software. Getesteter Code ist tendenziell modularer und lose gekoppelt – sonst wäre er nicht testbar. Eine gut gepflegte Sammlung an Tests bietet uns daher einen großen Mehrwert.“ 

Isolierte ES2015-Klassen oder Angular Test Bed?

Beim Unit-Testen einer Angular-Anwendung gibt es zwei Arten von Tests:

  1. Durch die Verwendung der ES2015-Module-API und durch die Kapselung von Funktionalität in isolierten Klassen bestehen Angular-Anwendungen oft aus von Angular unabhängigem Code. In diesem Fall liegt die eigentliche Business-Logik in Form einer regulären ES2015-Klasse vor. Beim Testen bietet diese Tatsache große Vorteile, da es hier leicht möglich ist, eine simple Instanz der Klasse zu erzeugen.
  2. Mithilfe des Angular-Testing-Frameworks kann man echte Angular-Komponenten (inklusive der gerenderten Templates) unabhängig von einer laufenden Anwendung testen. Wir können beispielsweise überprüfen, ob eine Komponente erstellt wurde, wie sie mit dem Template, mit anderen Komponenten und mit Abhängigkeiten interagiert.

Isolierte ES2015-Klassen

In einem isolierten Ansatz testen wir eine Direktive, die dafür zuständig ist, zu überprüfen, ob der Wert eines Controls eine gültige E-Mail-Adresse enthält.

Zuerst instanziieren wir die Klasse und testen dann, wie sie in verschiedenen Situationen funktioniert.

Listing zeigt die entsprechende Implementierung:

import{Directive}from'@angular/core';

import{NG_VALIDATORS}from'@angular/forms';

@Directive({

  selector:'[EmailValidator]',

  exportAs:'email-validator',

  providers: [

    {

      provide:NG_VALIDATORS,

      useClass:EmailValidatorDirective,

      multi:true,

    },

  ],

})

exportclassEmailValidatorDirective{

  validate(element:HTMLElement){

    constinput= <HTMLInputElement>element;

    constregex=

      /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;

    if (!input?.value||input.value===''||regex.test(input.value)) {

      returntrue;

    }else{

      returnfalse;

    }

  }

}

Vergessen Sie beim Schreiben von Tests nicht die Reihenfolge der Codeausführung. Zum Beispiel legen wir die Aktionen, die vor jedem Test ausgeführt werden müssen, in beforeEach.

Auch wenn es sich um eine vollständige Angular-Direktive handelt, liegt die eigentliche Business-Logik in Form einer regulären ES2015-Klasse vor. An der Stelle benötigen wir den Validator nicht durch das Angular-Framework erzeugen zu lassen, sondern einfach eine simple Instanz der Klasse zu erzeugen.

Listing zeigt somit einen vollständigen Testfall:

import{EmailValidatorDirective}from'./email-validator.directive';

describe('EmailValidatorDirective',()=>{

  letvalidatorDirective:EmailValidatorDirective|null=null;

  beforeEach(()=>{

    validatorDirective=newEmailValidatorDirective();

  });

  it('schould accept valid email addresses',()=>{

    // Arrange

    constexpectedColor='#00FF00';

    constcontrol= <HTMLInputElement>{

      value:'foo@bar.com',

      style:{borderColor:'#000000'},

    };

    // Act

    constresult=validatorDirective?.validate(control);

    // Assert

    expect(result?.style.borderColor).toEqual(expectedColor);

  });

  it('schould not accept invalid email addresses',()=>{

    // Arrange

    constexpectedColor='#FF0000';

    constcontrol= <HTMLInputElement>{

      value:'foo@barcom',

      style:{borderColor:'#000000'},

    };

    // Act

    constresult=validatorDirective?.validate(control);

    // Assert

    expect(result?.style.borderColor).toEqual(expectedColor);

  });

});

Angular Test Bed Tests

Einfache Komponente

Während Sie im vorigen Abschnitt einfach auf ES2015-Standardfunktionalität zurückgreifen konnten, müssen Sie im Fall von echten Komponententests dafür sorgen, dass Angular die für die Testausführung benötigten Komponenten kennt und deren Instanziierung vornimmt.

Lass uns nun die volle Leistungsfähigkeit des Dienstprogramms TestBed anschauen. Beginnen wir mit der einfachsten Komponente als Beispiel:

import{Component}from'@angular/core';

 

@Component({

  selector:'app-root',

  templateUrl:'./app.component.html',

  styleUrls: ['./app.component.scss'],

})

exportclassAppComponent{

  title='AngularTestDemo';

 

  constructor(){}

}

 

Template-Datei:

<div>

  <div>Input E-Mail</div>

  <input

    EmailValidator

    type="text"

    class="form-control"

    name="email"

  />

</div>

Analysieren wir die Testdatei Stück für Stück. Zuerst legen wir die TestBed-Konfiguration fest:

beforeEach(async()=>{

    awaitTestBed.configureTestingModule({

      declarations: [AppComponent]

    }).compileComponents();

  });

Im beforeEach-Hook wird zunächst das Testing-Modul konfiguriert. Sie können sich das hier erzeugte Modul als Ersatz für das echte (in der Datei app.module.ts) definierte Hauptmodul  der Applikation vorstellen. Hier wird das AppComponent zu den declarations hinzugefügt. Über die Methode configureTestingModule können Sie ebenfalls andere Module mit der impots-Eigenschaft oder Services mit der providers-Eigenschaft zum Modul hinzufügen.

compileComponents() ist eine Methode, die Stile und UI-Vorlagen in separate Dateien inline gerendert macht.

Dieser Prozess ist asynchron, da der Angular-Compiler Daten aus dem Dateisystem abrufen muss.

Tests erfordern, dass die Komponenten kompiliert werden, bevor sie über die Methode createComponent() instanziiert werden.

Daher haben wir den ersten BeforeEach in der async-Methode platziert, damit sein Inhalt in einer speziellen asynchronen Umgebung ausgeführt wird. Und bis die Methode compileComponents() ausgeführt wird, wird das folgende beforeEach nicht ausgeführt:

beforeEach(()=>{

    fixture=TestBed.createComponent(AppComponent);

    component=fixture.componentInstance;

  });

Dank der Platzierung aller gängigen Daten in beforeEach ist der weitere Code viel sauberer. Lassen Sie uns die Erstellung der Komponenteninstanz und ihrer Eigenschaft überprüfen:

it('should create the component',()=>{

    expect(component).toBeTruthy();

  });

 

  it(`should have as title 'app'`,()=>{

    expect(component.title).toEqual('AngularTestDemo');

  });

Komponente mit Abhängigkeiten

Lassen Sie uns unsere Komponente erweitern, indem wir einen PopupService einfügen:

import{Component}from'@angular/core';

import{PopupService}from'./services/popup.service';

 

@Component({

  selector:'app-root',

  templateUrl:'./app.component.html',

  styleUrls: ['./app.component.scss'],

})

exportclassAppComponent{

  title='AngularTestDemo';

  constructor(privatepopupService:PopupService){}

  openDialog(){

    this.popupService.openDialog();

  }

}

Template-Datei:

<div>

  <div>Input E-Mail</div>

  <input

    EmailValidator

    type="text"

    class="form-control"

    name="email"

  />

  <div>Open dialog</div>

  <buttonclass="openDialog"(click)="openDialog()">Open dialog</button>

</div>

Es scheint, dass sie es noch nicht kompliziert haben, aber die Tests werden nicht bestehen. Auch wenn Sie nicht vergessen haben, den PopupService zu den AppModule-Anbietern hinzuzufügen.

Denn in TestBed müssen sich auch diese Veränderungen widerspiegeln:

beforeEach(async()=>{

  awaitTestBed.configureTestingModule({

    declarations: [AppComponent,EmailValidatorDirective],

    providers: [

      {provide:PopupService,useValue:popupServiceMock},

    ],

  }).compileComponents();

});

Wir können den PopupService selbst angeben, aber normalerweise ist es am besten, ihn durch eine Klasse oder ein Objekt zu ersetzen, das genau beschreibt, was wir für unsere Tests benötigen.

Sie stellen sich einen PopupService mit einer Reihe von Abhängigkeiten vor und müssen während des Tests alles registrieren. Ganz zu schweigen davon, dass wir die Komponente in diesem Fall testen. Im Allgemeinen geht es beim Testen einer Sache nur um Unit-Tests.

Also schreiben wir den Mock wie folgt vor:

constpopupServiceMock={

  openDialog:()=>{},

};

Wir legen nur die Methoden fest, die wir testen. Fügen Sie der TestBed-Konfiguration Anbieter hinzu:

providers: [

  {provide:PopupService,useValue:popupServiceMock},

],

Verwechseln Sie popupServiceMock und PopupService nicht. Dies sind verschiedene Objekte: Das erste ist ein Klon des zweiten.
Großartig, aber wir haben den PopupService nicht einfach so umgesetzt, sondern für den Einsatz:

openDialog(){

  this.popupService.openDialog();

}

Jetzt lohnt es sich sicherzustellen, dass die Methode tatsächlich aufgerufen wird. Dazu erhalten wir zunächst eine Instanz des Dienstes.
Da in diesem Fall der Dienst in den Anbietern des Root-Moduls angegeben ist, können wir dies tun:

letdebugElement:DebugElement;

letfixture:ComponentFixture<AppComponent>;

letpopupService:PopupService;

fixture=TestBed.createComponent(AppComponent);

debugElement=fixture.debugElement;

popupService=debugElement.injector.get(PopupService);

Zum Schluss die Prüfung selbst:

it('should called openDialog',()=>{

  constopenSpy=spyOn(popupService,'openDialog').and.callThrough();

  debugElement.query(By.css('button.openDialog')).triggerEventHandler('click',     null);

  expect(openSpy).toHaveBeenCalled();

});

Setzen Sie den Spy auf der openDialog-Methode des popupService-Objekts.

Beachten Sie, dass wir den Aufruf der Servicemethode überprüfen, nicht die Rückgabe oder andere Dinge über den Service selbst. Sie sollten im Dienst getestet werden.

Nur bestimmte Tests ausführen (xdescribe, xit, fdescribe, fit)

Um bestimmte Test-Suites oder Tests zeitweise abzuschalten, stehen Ihnen die beiden Funktionen xdescribe und xit zur Verfügung. Ersetzen Sie hierfür einfach die Schlüsselwörter describe bzw. it durch das jeweilige Pendant. Folgende Listing zeigt die Deaktivierung des Tests auf Aufruf von OpenDialog:

xit('should called openDialog',()=>{

  constopenSpy=spyOn(popupService,'openDialog').and.callThrough();

  debugElement.query(By.css('button.openDialog')).triggerEventHandler('click',null);

  expect(openSpy).toHaveBeenCalled();

});

Das Gegenstück hierzu bilden die beiden Funktionen fdescribe und fit. Ihre Verwendung führt dazu, dass nur noch die ausgewählte Suite oder der ausgewählte Testfall ausgeführt wird:

fit('should called openDialog',()=>{

  constopenSpy=spyOn(popupService,'openDialog').and.callThrough();

  debugElement.query(By.css('button.openDialog')).triggerEventHandler('click',null);

  expect(openSpy).toHaveBeenCalled();
});

 

Fazit

In diesem Blog haben wir Jasmine für Angular 12-Entwickler vorgestellt und erfahren, wie Sie mit Jasmine beginnen, um Ihren JavaScript-Code zu testen. Es ist schwierig, alle Momente in einem Artikel abzudecken, aber es scheint mir, dass die Hauptsache darin besteht, zu verstehen, welche Werkzeuge Sie haben und was Sie damit tun können, und sich dann in der Praxis furchtlos sowohl mit einfachen als auch mit komplexeren Fällen zu stellen.

Quellcode

https://github.com/ArkadiRosenberg/AngularTestDemo.git

Literatur

  • Angular: Das große Handbuch zum JavaScript-Framework. Einführung und fortgeschrittene TypeScript-Techniken, Inkl. Angular Material Gebundene Ausgabe – 28. Januar 2019

von Christoph Höller 

Dieser Tipp kommt von Arkadi Rosenberg, QUIBIQ Stuttgart.

Ihre Kontaktmöglichkeiten

Sie haben eine konkrete Frage an uns


 

Bleiben Sie immer auf dem Laufenden


 

Mit meinem "Ja" erkläre ich mich mit der Verarbeitung meiner Daten zur Zusendung von Informationen einverstanden. Ich weiß, dass ich diese Erklärung jederzeit durch einfache Mitteilung widerrufen kann. Bei einem Nein an dieser Stelle erhalte ich zukünftig keine Informationen mehr.

© QUIBIQ GmbH · Impressum · Datenschutz