Angular E2E mit json-server

Ein E2E-Test dient dazu eine Anwendung „von Vorne bis Hinten“ durchzutesten. Dabei sind viele Hürden zu meistern, angefangen von erreichbarer Infrastruktur, über Authentifizierung bis hin zu konstanten Datenbeständen. Das mag in einigen Projekten nur schwer und mit viel Aufwand realisiert bar sein. Ein Unit-Testing in Angular ist hier in vielen Fällen kaum ein ausreichender Ersatz. Also muss ein Mock her, um die Datenschnittstellen abzulösen. Eine praktikable Lösung: json-server

„Get a full fake REST API with zero coding“ das ist json-server. Die Grundlage dafür ist ein Node-Projekt: https://github.com/typicode/json-server + in der einfachsten Form eine projektspezifische JSON Datei die unseren Daten enthält. Eine solche Datei „db.json“:

  {
    "data": [
      {
        "id": 1,
        "message": "Lorem ipsum"
      },
      {
        "id": 3,
        "message": "Clita kasd gubergren"
      },
      {
        "id": 4,
        "message": "Takimata sanctus est Lorem"
      }
    ],
    "demo": {
      "message": "Demo Object"
    }
  }
  

und der Start von json-Server (Default Port: 4000) :

json-server --watch db.json

Fertig ist unser „backend“. In unserem Beispiel generiert json-server basierend auf unserer json-Datei zwei Rest-Schnittstellen: http://localhost:4000/data und http://localhost:4000/demo die sich direkt Abfragen lassen und die angegebenen Daten liefern. Aber nicht nur das. Auch POST/PUT/DELETE Funktionen werden direkt unterstützt. Sollte die zu simulierende Schnittstelle komplexer sein gibt es die Möglichkeit individuelle Routen anzugeben oder sogar programmatisch auf bestimmte Anfragen zu reagieren.

+ Protractor

Um das zusammen mit Protractor zu nutzen sind folgende Schritte zu implementieren

  1. für unsere Tests benötigen wir den laufenden JSON-Server. Charmant: wir starten den Server in der prepare Methode von Protractor mittels _child_process_
  onPrepare() {
    const { spawn } = require('child_process')
    const serverPath = require('path').join(__dirname, './json/server.js');
    spawn('node', [serverPath]);
    ...

(protractor.conf.js , wir verwenden hier ein individuelles json-server-Script, um wie bereits angesprochen programmatisch auf einige Anfragen zu reagieren, siehe server.js-Demo + Doku „Module“ )

2. wir verwenden eine proxy-Konfiguration um alle Anfragen an bestimmte URLs (unsre Backend) auf unseren json-server um zu leiten:

{
  "/api/*": {
    "target": "http://localhost:4000",
    "secure": false,
    "changeOrigin": true,
    "logLevel": "debug",
    "pathRewrite": {
      "^/api/": "/"
    }
  }
}
alle Anfragen an "http://.../api/... werden umgeleitet auf http://localhost:4000/...

3. eine solche Proxy-Konfiguration lässt sich eigentlich beim serve-Befehl mittels „-proxy-config“ übergeben. Leider unterstützt das E2E-Ziel diese Option nicht ( s. github issue ). Stattdessen müssen wir die entsprechende Konfiguration ergänzen die wir dann beim E2E-Target verwenden( angular.json ).

      "architect": {
       ...
        "serve": {... },
        "serve-e2e": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "ng-json-server:build",
            "proxyConfig": "e2e/proxy.conf.json",
            "port": 4300
          },

....
        "e2e": {
          "builder": "@angular-devkit/build-angular:protractor",
          "options": {
            "protractorConfig": "e2e/protractor.conf.js",
            "devServerTarget": "ng-json-server:serve-e2e"
          },
          "configurations": {
            "production": {
              "devServerTarget": "ng-json-server:serve-e2e:production"
            }
          }
        }

Vollständiges Beispiel @GitHub

Das war’s. Ein

npm run e2e

Startet nun unseren JSON-Server, konfiguriert den Development Server mit unserem Proxy und lässt alle E2E-Tests Laufen.

Alles. Live. In Farbe. @GitHub

Angular mit Zeitstempel und Version

Selbst bei einfachstem Test- und Release-Vorgehen sollte immer klar sein welche Version der Anwendung man gerade vor sich hat. Hilfreich ist es dabei einen Zeitstempel und/oder die aktuelle Version innerhalb der Anwendung sichtbar zu machen.

Version

Die Version unserer Anwendung finden wir in unser package.json. Diese Datei ist aber natürlich nicht Teil unseres ausgelieferten Bundles. Diese Datei beim build einfach zu kopieren und somit verfügbar zu machen mag einfach sein, offenbart aber potenziellen Angreifern eine Vielzahl von Informationen über unser Projekt und dessen Abhängigkeiten. Stattdessen importieren wir in unserer Komponente die package.json und ermöglichen unserem TypeScript Compiler die Version zu extrahieren:

import {version} from '../../package.json';
...
@Component({})
export class AppComponent {
  version: string;

Damit TypeScript diesen Zugriff auf eine einfache JSON-Datei per Import erlaubt, reicht es aus in unserer tsconfig folgenden Eintrag zu ergänzen:

    "resolveJsonModule": true,

Zeitstempel

Der Zeitstempel stellt tatsächlich eine größere „Herausforderung“ dar. Unser Vorgehen: ein Eintrag in unserer Environment soll soll dazu dienen den Zeitstempel in der Anwendung verfügbar zu machen. Damit lässt sich eine Ausgabe in unserer Komponente ganz einfach realisieren

    this.version = `${version} - ${environment.buildTime}`

Unsere environments bereiten wir mit einem entsprechenden Eintrag vor:

export const environment = {
  buildTime: '',
...

Diese Variable muss aber nun natürlich beim Build auch gesetzt werden. Dazu verwenden wir ein NPM-Tool:

replace-in-file – npm (npmjs.com)

Damit können wir mittels node-Script, einen beliebigen Text-Ersatz innerhalb von Dateien ausführen. Ein entsprechendes Script könnte so aussehen. Dabei ermöglichen wir auch das „zurücksetzen“ auf einen Default Wert. Hintergrund: wir wollen keine unnötigen Dateiänderungen in unserem Versionskontrollsystem nach einem Build:

let replace = require("replace-in-file");
let buildTime = new Date().toISOString();
let revert = process.argv[2];

const options = {
  files: "src/environments/*.ts",
  allowEmptyPaths: false,
  from: /buildTime.*/g,
  to: `buildTime: '${revert ? '' : buildTime}',`
};

try {
  replace.sync(options);
} catch (error) {
  console.error("Error while setting build time:", error);
}

Anpassung an unserem npm-build-script:

"build": "node ./replace.build.js 
           &&  ng build --prod --base-href . 
           && node ./replace.build.js revert",

…und schon landet eine kleine nette Info in unserer Anwendung:

Weihnachten 2020

GEDOPLAN wünscht Frohe Weihnachten 2020

https://gedoplan.github.io/ged-xmas2020/index.html

source: https://github.com/GEDOPLAN/ged-xmas2020

Richfaces und JSF 2.3.X

Jeder der sich gerade die Augen reibt: ja er hat „Richfaces“ geschrieben. Auch wenn Richfaces das Ende seines Lebens schon lange erreicht hat existieren hier und da noch einige Projekte die sich auf die Funktionalität eben dieser Bibliothek verlassen

…trotzdem sollten wir natürlich damit unseren Aktualisierungswunsch aller anderen Komponenten/Bibliotheken nicht zu den Akten legen. Insbesondere ein Update der Core-JSF Bibliothek ist aus Security Sicht stark anzuraten, werden doch immer mal wieder wichtige Sicherheitslücken geschlossen. Das ist in einem Application Server (selbst ohne diesen komplett zu aktualisieren) in aller Regel kein Problem, „javax.faces.jar“ austauschen und fertig. Nun ist (sagen wir „war“?) Richfaces dafür bekannt nicht gerade tolerant mit Versionswechseln jeglicher Art um zu gehen. Ein Blick in die Anwendung lässt auch hier kein gutes Gefühl aufkommen: „irgendwie langsamer“. Und das aus gutem Grund. Wirft man einen Blick in die generierten HTML-Ausgaben finden wir die Antwort:

Richfaces lädt, je nach Seite, gleich dutzendfach seine benötigten Ressourcen und bremst die Anwendung spürbar aus. Dabei wird die packed.js von Richfaces eigentlich dazu verwendet um benötige Ressourcen zu bundeln und performant in nur einem Request abzufragen.

Eine einfache und pragmatische Lösung: die Ressourcen-Optimierung deaktivieren. Dazu reicht ein entsprechender Eintrag in der web.xml:

    <context-param>
        <param-name>org.richfaces.resourceOptimization.enabled</param-name>
        <param-value>true</param-value>
    </context-param>

Zwar generieren wir damit immer noch mehr Requests als es eigentlich sein müssten, da die benötigten Ressourcen nun einzeln abgefragt werden. Dafür passiert das nun aber auch nur noch einmal.

Ende gut. Alles gut. Vielleicht insbesondere bei Richfaces ein passender Schlusssatz 😉 .