Angular Testing CodeCoverage

Wer Unit-Tests für seine Anwendungen schreibt ( also jeder von uns 😉 ) wird kaum in der Lage sein ohne Tooling zu Überblicken welche Teile der Software getestet wurden und welche nicht. Angular macht es uns da zum Glück sehr leicht. Dank einer simplen Option in der angular.json Konfiguration:

  ...
"test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            ...
            "codeCoverage": true
          }
        },
...

Dank dieser wird bei jedem Test-Durchlauf ein neuer ein Bericht generiert der als HTML-Seite abgerufen werden kann:

[project-root]/coverage/index.html

Neben der groben Übersicht welche Pfade der Anwendung mit welcher Test-Abdeckung versehen sind lässt sich hier bequem bis auf Datei-Ebene navigieren um so die Stellen zu erkennen die möglicherweise noch nicht ausreichen mit Unit-Tests abgedeckt sind:

Werbeanzeigen

Angular Testing mit Spies

Unit Testing gehört zum guten Ton eines jeden Projektes. Angular tut sein bestes um es dem Entwickler so leicht wie möglich zu machen dieses Thema im Projektalltag unter zu bringen. Es existieren mehrere Möglichkeiten Komponenten losgelöst zu testen. Eine davon: ‚Spies‘

Schauen wir uns zuerst noch einmal kurz in groben Zügen das Testing von Angular an sich an ( weiterer Blog beitrag zu diesem Thema hier ) . Sofern wir es nicht deaktiviert haben generiert die Angular CLI zu jeden unserer Komponenten, Service etc. eine zusätzliche TypeScript Datei mit der Dateiendung: spec.ts . Diese Datei ist der Ausgangspunkt unseres Unit-Tests und enthält einen ersten sehr rudimentären Test ob die Komponente erstellt werden kann:

describe('LocalStorageTextAreasComponent', () => {
  let component: LocalStorageTextAreasComponent;
  let fixture: ComponentFixture<LocalStorageTextAreasComponent&gt;;

  beforeEach(async(() =&gt; {
    TestBed.configureTestingModule({
      declarations: [ LocalStorageTextAreasComponent ]
    })
    .compileComponents();
  }));

  beforeEach(() =&gt; {
    fixture = TestBed.createComponent(LocalStorageTextAreasComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () =&gt; {
    expect(component).toBeTruthy();
  });
});

Schauen wir uns die wichtigen Bestandteile noch mal an und machen uns klar welche Aufgabe sie haben:

TestBed . Erzeugt ein eigenständiges NGModul welches zum Testen herrangezogen wird. Das bedeutet das wir hier wie gewohnt mit declarations, imports und providers deklarieren was wir verwenden wollen. Im Fall unserer Unit Tests besteht hier z.B. die Möglichkeit Objekte die wir per DI erhalten zu mocken:

    TestBed.configureTestingModule({
      providers: [
        { provide: RecordService, useValue: { save: () => { } } },
        { provide: ProjectService, useValue: { getAll: () => { } } },
      ]
    })

fixture.componentInstance liefert uns dann die konkrete Instanz die wir zum Testen herran ziehen können. Diese Komponente hat natürlich unter umständen Abhänigkeiten und verwendet z.B. Services die bei unseren Tests in aller Regel nicht aufgerufen werden sollen: wir wollen ja unsere Komponente testen und nicht die Services die sie verwendet. Neben der Erzeugung eines kompletten Mock-Objektes und die Verwendung mittels providers-Array existiert noch die Möglichkeit einen Spy zu verwenden. Quasi ein „Beobachter“ der für Methoden registriert wird und über den wir dann,zum einen, Prüfen können ob die Methoden aufgerufen wurden und zum anderen können wir auch dafür sorgen das Test-Werte geliefert werden anstatt den Aufruf an die echte Methode zu delegieren.

  it('close should save', () => {
    const emitSpy = spyOn(component.saved, 'emit');

    const saveSpy = spyOn(component['recordService'], 'save').and.returnValue(of({ id: 100 }));

    component.form.get('id').setValue(null);
    component.form.get('start').setValue(new Date());

    component.beforeunload(null);
    expect(saveSpy).toHaveBeenCalled();
    expect(emitSpy).toHaveBeenCalledWith({ id: 100 });

  });

Zwei Beispiel dafür sehen wir hier. Zeile 2 registriert einen Spy der auf die „save“ Methode der zu testen Komponente registriert wird. Wir lassen den Aufruf zu dieser Methode zu, können aber über den Spy am Ende prüfen ( Zeile 11 ) ob die Methode aufgerufen wurde + bei Bedarf unter Angabe von konkreten Parametern die wir erwarten.

Zeile 4 zeigt nun die Verwendung von einem Spy um Service-Aufrufe zu unterbinden. Ähnlich wie im ersten Beispiel registrieren wir uns mittels spyOn Methode unter Angabe des Objektes und dem Namen der Methode. Hier verknüpfen wir den Spy jedoch mittels and.returnValue und liefern dann ein Observable der ein Dummy-Objekt zurück liefert. Damit können wir die Komponente testen ohne das die Service-Methode aufgerufen wird.

Angular – WebWorker

Der Browser dient schon lange nicht mehr nur als einfaches Anzeigeinstrument für Webseiten. JavaScript übernimmt mehr und mehr Aufgaben die früher auf dem Server abgewickelt werden mussten, dank performanter Endgeräte aber zunehmend auch im Browser behandelt werden. Nun ist es aber so das JavaScript single-threaded ist und CPU-intensive Funktionalitäten zum einfrieren der GUI führen. Abhilfe? WebWorker!

CSS-Animation + rechenintensive Aktion. Einmal ohne ( submit ) und einmal mit webworker

WebWorker ermöglichen es uns parallelen Code aus zu führen, also abseits des Threads der sich um die Abwicklung der GUI Interaktion kümmert. Technische Vorraussetzung dafür ist das der Code des WebWorkers in einer separaten Datei abgelegt wird und mittels asyncronen Event-Handling mit der Anwendung kommuniziert. Insbesonderer der erste Teil hat es vor der Angular Version 8 schwierig gemacht mit diesen zu arbeiten, sorgt Angular durch seinen Buildprozess für das Zusammenführen aller Dateien. Seit Angular 8 unterstützt die Angular-CLI aber die Generierung für WebWorker:

ng generate web-worker [name]

Neben der Erstellung einer entsprechenden Worker-Datei bereitet Angular unsere Anwendung auch für diese vor. So wird neben einer Anpassung an der CLI( angular.json ) auch die TypeScript-Konfiguation angepasst um das spätere Zusammenführen der Dateien zu umgehen. Ein WebWorker besteht dann aus einer einfachen Funktion die Parameter entgegen nimmt und und über eine ensprechende Funktion eine Rückgabe liefern kann:

addEventListener('message', ({ data }) =&gt; {
  for (let i = 0; i < data.times; i++) {
    data.result = data.result * data.multiplier;
  }
  postMessage(data.result);
});

Die Verwendung dieser Funktion ähnelt dann ein wenig der Verwendung von asycronen Service-Methoden, mit dem Unterscheid das wir im Vorfeld explizit ein WebWorker Objekt erzeugen:

  calculateWW() {
    const worker = new Worker('./multiplier.worker.ts', { type: 'module' });
    worker.onmessage = ({ data }) => {
      this.form.get('result').setValue(data);
    };

    this.form.get('result').setValue(this.form.get('initValue').value);
    worker.postMessage(this.form.value);
  }

So registrieren wir einen EventListener der benachrichtigt wird wenn der Worker eine Rückgabe liefert ( Zeile 4 ) und rufen den Worker mit frei wählbaren Parametern auf( Zeile 8 )

Github? Github !

Angular + OpenAPI Generator

In einem älteren Posting haben wir schon einmal einen Blick auf Swagger geworfen, eine charmante Möglichkeit aus den eigenen Rest-Schnittstellen eine technisch auswertbare OpenAPI Beschreibung zu generieren. Swagger bietet neben dieser Core-Möglichkeit noch eine ganze Reihe mehr, z.B. die Generierung von Client-Stubs. Das entsprechende Projekt ( Codegen ) wurde gefühlt eine ganze Zeitlang nicht aktiv verfolgt. Inzwischen lebt die Idee in einem Community getriebenem Projekt weiter: OpenAPI Generator. Werfen wir einen kurzen Blick auf die Verwendung im Zusammenspiel mit Angular.

Die Open API Spezifikation ist eine Standardbeschreibung für Schnittstellen die im JSON oder YAML Format Rest Schnittstellen (inklusive URL, Authentifizierung und Datenmodell) beschreibt. Eine Verwendungsmöglichksit ist die Generierung von Client-Code der die Entwicklung z.B. eines Anuglar-Clients erleichtert.

Der OpenApi Generator bietet hierbei unter anderem mit einem NPM-Tool welches ganz einfach per Kommandozeile installiert werden kann

npm install @openapitools/openapi-generator-cli -g

ein einfacher Aufruf zur Generierung von Angular-Sourcen könnte dann so aussehen:

openapi-generator generate -g typescript-angular -o src/app/api -i ./openapi.json

auf Basis der übergebenen API Spezifikation werden nun entsprechende Type-Script Sourcen generiert für

unsere Model-Klassen:

/**
 * The version of the OpenAPI document: 3.2
 *
 * NOTE: This class is auto generated by OpenAPI Generator 
(https://openapi-generator.tech).
 * Do not edit the class manually.
 */


export interface Project { 
    id?: number;
    projectId?: string;
    projectName?: string;
}

und eine zentrale Service-Klasse, die solche Methoden entsprechend unserer Resourcen generiert (unter Berücksichtigung von Pfadparametern und Rückgabewerten) (vereinfachte Darstellung):

public getAllProjects(limit?: number, first?: number): Observable<Project[]&gt; {
    ...
        return this.httpClient.get<Array<Project&gt;&gt;(`${this.configuration.basePath}/resources/project`,
            {
                params: queryParameters,
                withCredentials: this.configuration.withCredentials,
                headers: headers,
                observe: observe,
                reportProgress: reportProgress
            }
        );
    }

Was nun lediglich noch fehlt für den Projektalltag ist die

  • Konfiguration der Base-URL, vorzugsweise über einen Provider, hier z.B. über die environmen.ts
@NgModule({
 ...
  providers: [
    {
      provide: BASE_PATH,
      useValue: environment.baseurl
    }
  ]
  ...
  • Login Credentials über HTTP-Interceptor hinzufügen
@Injectable({
  providedIn: 'root'
})
export class HttpJwtInterceptorService implements HttpInterceptor {
  constructor(private loginService: LoginService) {}

  intercept(req: HttpRequest<any&gt;, next: HttpHandler): Observable<HttpEvent<any&gt;&gt; {
    const token = this.loginService.token;

    if (token) {
      req = req.clone({
        setHeaders: {
          Authorization: 'Bearer ' + this.loginService.token
        }
      });
    }
    return next.handle(req);
  }
}


// + Registrierung im Modul
@NgModule({
 ...
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      multi: true,
      useClass: HttpErrorInterceptorService
    }
  ]
  ...

Grundsätzlich eine sehr coole Sache, die uns viel Tipp-Arbeit spart. Im Hinterkopf sollte man aber immer behalten das wir damit unser Projekt auf zwei Generatoren stützen: 1) Swagger-Core um unsere OpenAPI zu generieren und 2) OpenAPI Generator um unsere Angular Artefakte erstellen zu lassen . Hier werden wir über kurz oder lang bei „komplexeren“ Schnittstellen an den Punkt kommen an dem einer der Generatoren nicht mehr das liefert was wir uns wünschen. Hier heißt es dann gegebenenfalls „händisch“ nach zu arbeiten. Eine Möglichkeit im Anuglar: generell mit einem Service-Delegate arbeiten: damit zentralisieren wir den Zugriff auf die generierten Teile unserer Anwendung und können hier auch zusätzliche Funktionalitäten implementieren ( z.B. „Caching“) und fachliche Gruppen („ProjektService“, „CustomerService“) ausprägen, auch wenn das wieder ein klein wenig Tipparbeit ist 😉

@Injectable({
  providedIn: 'root'
})
export class ProjectService {
  private projects: Project[];

  constructor(private service: DefaultService) {
  }

  getAll(limit?: number): Observable<Project[]&gt; {
    if (!this.projects) {
      return this.service.getAllProjects(limit).pipe(tap(r =&gt; (this.projects = r)));
    } else {
      return from([this.projects]);
    }
  }
}

OpenCSV

XML und JSON sind fantastische, leichtgewichtige und technisch gut zu verarbeitende Schnittstellen-Formate. Es gibt da aber ein weiteres Format, welches den Beiden den Rang abläuft, zumindest wenn es um die inflationäre Verwendung geht: CSV. Wenn wir einmal nicht darum herum kommen CSV Dateien zu lesen oder zu schreiben, sollten wir zumindest versuchen dies auf eine Art und Weise zu tun die unsere Nerven schont. Ein solcher Weg: OpenCSV

Die Grundidee hinter OpenCSV ist weder neu noch kompliziert. Ähnlich wie bei JPA, JSON oder XML versehen wir unser Model oder DTO-Klassen mit Annotationen die später für das Lesen / Schreiben der Daten sorgen soll. Später im Programm übergeben wir den entsprechenden Funktionen nur einen InputStream und eine so annotierte Klasse und für jede Zeile innerhalb unserer CSV Datei erhalten wir eine Objekte-Instanz ( bzw. beim Schhreiben: für jede Objektinstanz eine Zeile in der CSV Datei).

Für die einfachste Form dieser Annotationen existieren zwei Varianten:

    @CsvBindByPosition(position = 0)
    private Long id;

    @CsvBindByName(column = "customerid")
    private Long customerid;

Hier wird festgelegt ob das Mapping basierend auf der Position (Variante 1) oder basierend auf einer Kopfzeile innerhalb der CSV Datei (Variante 2) geschehen soll. Das Einlesen einer CSV Datei ist dann schnell erledigt:

demo.csv

customerid;firstname;lastname;registerdate;discount
1;Max;Muster;01.05.2010;5
2;Adam;Bean;20.06.2011;10
3;Lisa;Müller;08.08.2012;15
4;Lischen;Müller;27.09.2013;20

einlesen:

 List<Customer&gt; beans = new CsvToBeanBuilder<Customer&gt;(...inputstream...)
               .withSeparator(';')
               .withType(Customer.class)
               .build().parse();

Der Vorteil liegt auf der Hand. Dank der Annotationen haben wir eine sehr gut verständliche und wartbare Struktur welche die CSV Datei wiederspiegelt.

Oftmals reicht ein solches „einfaches“ Mapping natürlich nicht aus, da wir möglicherweise ganze Objektgraphen in eine flache CSV-Struktur bringen wollen. Zum einen wird beim Schreiben von CSV Dateien der entsprechende Getter aufgerufen wo ein einfaches Mapping statt finden kann. Flexibler und nicht nur für das Schreiben sondern auch für das Lesen von CSV Dateien sind Converter die registriert werden können:

public class Material {

    @CsvBindByPosition(position = 0)
    private Long id;

    @CsvBindByPosition(position = 1)
    private String description;

    @CsvBindAndSplitByPosition(position = 2, elementType = Price.class, splitOn = "\\|", converter = PriceConverter.class)
    private List<Price&gt; prices;

    @CsvBindByPosition(position = 3)
    private Double averagePrice;

    public Double getAveragePrice() {
        return prices.stream().map(Price::getPrice)
                .mapToDouble(BigDecimal::doubleValue)
                .average()
                .orElse(0.);
    }
}

PriceConverter.java :

public class PriceConverter extends AbstractCsvConverter {

    private final DecimalFormat decimalFormat = new DecimalFormat("#,##");

    @Override
    public String convertToWrite(Object value) throws CsvDataTypeMismatchException {
        final Price price = (Price) value;
        final String priceValue = decimalFormat.format((price).getPrice());
        final String until = price.getValidTo().format(DateTimeFormatter.ISO_DATE);
        final String from = price.getValidTo().format(DateTimeFormatter.ISO_DATE);
        return String.format("%s*%s*%s|", from, until, priceValue);
    }

    @Override
    public Object convertToRead(String value) throws CsvDataTypeMismatchException, CsvConstraintViolationException {
        final String[] splits = value.trim().split("\\*");
        LocalDate from=LocalDate.parse(splits[0], DateTimeFormatter.ISO_DATE);
        LocalDate to=LocalDate.parse(splits[1], DateTimeFormatter.ISO_DATE);
        BigDecimal price = new BigDecimal(splits[2]);
        return new Price(from, to, price);
    }
}

Für die Vollständigkeit, das Schreiben:

        Writer writer = new FileWriter("out.csv");
        StatefulBeanToCsv beanToCsv = new StatefulBeanToCsvBuilder(writer).withSeparator(';').build();
        beanToCsv.write(materials);
        writer.close();

Es gibt noch einige mehr Annotationen ( Datumsformat, individuelles Mapping für Schreiben und Lesen…) mit dessen Hilfe wir in den Prozess eingreifen können. Die Bibliothek ist bezüglich seines Umfangs sehr übersichtlich, bietet aber genug Stellen sehr individuell auf das Schreiben und Lesen von CSV Dateien ein zu wirken. Allemal ein Blick Wert.

Github? Klar.

JSF+Bean Validation, Groups – switch

Bean Validation (JSR 380) und Jave Server Faces sind inzwischen ein eingespieltes Team und werden in den meisten Projekte eingesetzt. Ein nettes Features welches wir schon in einem älteren Blog-Beitrag beleuchtet haben ( hier ) sind Validierungs-Gruppen mit denen wir unterschiedliche Regel-Sets zusammenstellen können

Wie schon im angesprochenen Artikel erläutert arbeiten wir bei den BeanValidation Groups mit einem Marker-Interface:

public class DemoModel {

    @NotNull(groups = OnTransmit.class)
    @Size(min = 4, groups = OnTransmit.class)
    private String firstname;

    @NotNull(groups = {Default.class, OnTransmit.class})
    @Size(min = 4, groups = {Default.class, OnTransmit.class})
    private String lastname;
}

Diese Gruppen lassen sich dann entwede programmatisch bei der Validierung heranziehen oder seit JSF 2.1 mittels f:validateBean in unserem JSF Template referenzieren. Doch was tun wenn wir dynamisch entscheiden wollen welche Gruppen zur Validierung heran gezogen werden sollen? Sagen wir auf Basis des verwendeten Buttons? Das macht die Sache hier schon etwas schwieriger. JSF bietet hier allerdings eine Möglichkeit, in der faces-config.xml registrieren wir einen globalen Validator:

    <application&gt;
        <default-validators&gt;
            <validator-id&gt;ConditionalBeanValidator</validator-id&gt;
        </default-validators&gt;
        <validator&gt;
            <validator-id&gt;ConditionalBeanValidator</validator-id&gt;
            <validator-class&gt;de.gedoplan.blog.jsf.validation.ConditionalBeanValidator</validator-class&gt;
        </validator&gt;
    </application&gt;
public class ConditionalBeanValidator extends BeanValidator {

    @Override
    public void validate(FacesContext context, UIComponent component, Object value) {
        setValidationGroups(retrieveValidationGroup(context));
        super.validate(context, component, value);
    }

    private String retrieveValidationGroup(FacesContext context) {
        if (context.getExternalContext().getRequestParameterValuesMap().containsKey("GEDTRANSMIT")) {
            return OnTransmit.class.getName();
        }

        return null;

    }
}

Dieser Validator ( implementiert javax.faces.validator.BeanValidator ) kann nun entscheiden/festlegen welche Bean Validation Gruppen verwendet werden sollen, ganz global, ohne das wir jedes einzelne Eingabefeld mit einem entsprechenden Binding versehen müssen. Idealerweise dient dafür ein fachliches Statusfeld, sollte so etwas nicht vorhanden sein ist auch folgendes Vorgehen über einen entsprechenden HTTP Parameter der beim Button angegeben wird denkbar:

                <h:commandButton action="#{demoController.submit()}" value="submit" id="submit"/&gt;
                <h:commandButton action="#{demoController.transmit()}" value="transmit" id="transmit"&gt;
                    <f:param name="GEDTRANSMIT" /&gt;
                </h:commandButton&gt;

Achtung! An dieser Stelle ist vorsicht geboten = in diesem Beispiel wird die Auswahl der Validierungs-Gruppe über einen Request-Parameter gesteuert, technisch wäre es durch Manipulation des HTTP Requests also möglich die transmit() Methode auf zu rufen ohne das die korrekte Validierung abläuft. Bei kritischen Prozessen sollte hier in der „transmit“ Methode (bzw. in einem entsprechenden Service) die korrekte Validierung (nocheinmal) durchgeführt werden oder zumindest das Vorhandensein des Parameters geprüft werden.

Github? Klaro

https://github.com/GEDOPLAN/jsf-custom-validator

NetBeans – Ordner ausblenden

Immer mal wieder kommt es vor das wir in unseren Projekt-Ordnern Inhalte vorfinden die nicht teil unserer direkten Arbeiten sind. Das können generierte Bestandteile aus anderen Prozessen sein oder in der Web-Entwicklung so etwas wie der „node-modules“ Ordner ( Projekt Abhänigkeiten ).

Das macht unsere IDE zum einen unnötig langsam da diese Ordner / Strukturen ja genauso analysiert und ausgewertet werden wie unsere eigenen Sourcen und auf der anderen Seite meldet NetBeans hier regelmäßig Fehler auf die wir jedoch keinen Einfluss haben. Es gab einmal ( in PHP Projekten wohl immer noch ) eine Möglichkeit in NetBeans Ordner für die IDE aus zu blenden. Diese Funktion ist leider verschwunden und steht für Java- und HTML5-Projekte nicht zur Verfügung. In einem NetBeans-BugTicket zu dem Thema hat jemand eine schnelle und einfach Lösung entwickelt: https://netbeans.org/bugzilla/show_bug.cgi?id=238709#c28

Durch ein kleine Plugin welches installiert werden kann ( getestet mit NetBeans 8,9 und 10) und das Anlegen einer Datei „.nbignore“ im betreffenden Ordner wird dieser einfach ausgeblendet.

Optimal ist die Lösung sicherlich nicht: der Ordner verschwindet aus der Anzeige der IDE vollständig und kann nur über den normalen Explorer noch bearbeitet werden das muss dem Entwickler bewusst sein. Zudem ist es unschön IDE-spezifische Dateien in unseren Projekten ab zu legen. Eine einfache und prakmatische Lösung ist es trotzdem und allemal besser als falsche Fehlermeldungen. Bleibt zu hoffen das wir diese Funktion eines Tages auch nativ wieder in NetBeans finden werden…