Testing

Recap: Testing Pyramide

JavaScript Unit Testing

  • wie eingangs erwähnt nutzt Angular Karma als Test Runner und Jasmine als Testing Framework
  • Jasmine ist stark an BDD (Behavior Driven Development) angelehnt
  • mit describe beschreibt man ein Szenario
  • mit it beschreibt man einen Testfall
  • die beiden können beliebig verschatelt werden

Ein erstes Beispiel

                    
                        describe('the pizza service', () => {

                            it('should have getPizze', () => {
                                let pizzaService = new PizzaService();

                                expect(service.getPizze).toBeDefined();
                            });

                            describe('getPizze should return salami pizza', () => {
                                it('should return some pizza', () => {
                                    let pizzaService = new PizzaService();
                                    let salami = new Pizza('Salami');

                                    let pizze: Pizza[] = service.getPizze();

                                    expect(pizze).toContain(salami);
                                });
                            });
                        });
                    
                

Jasmine vs JUnit

  • @Test => it('should ...')
  • @Ignore => xdescribe, xit
  • Excecute one test => fdescribe, fit
  • Assert => expect
  • @BeforeClass => beforeAll
  • @AfterClass => afterAll
  • @Before => beforeEach
  • @After => afterEach

Matchers und Doku

  • Dokumentation mit Beispielen
  • Matcher Syntax: expect(object).toBe(expected) => object === expected
  • toBeDefined(), toBeTruthy(), toBeUndefined(), toEqual(object), toBeNull(), toMatch(regex)

Spys

  • spyOn(object, methodName) => Spy
  • let pizzaSpy = spyOn(pizzaService, 'getPizze');
  • pizzaSpy.and.returnValue(pizzaArr);
  • pizzaSpy.callFake(() => pizzaArr);
  • expect(pizzaSpy).toHaveBeenCalled();

Ausführliches Beispiel

  • findet ihr im Branch Testing_Intro

Eine simple Komponente testen

  • Es gibt verschiedene Möglichkeiten Tests in Angular auszuführen
  • wir starten mit den Komponenten Tests
  • Wir testen die Logik einer Komponente

Angular Testing Utils: TestBed

  • TestBed erstellt ein eigenes Angular Modul, nur für diesen Test
  • die Methode configureTestingModule erwartet eine Modulkonfiguration mit declarations, providers etc.
  • Im Test hängt unsere Komponente nicht am GesamtModul der Applikation, sondern in diesem Testmodul
  • Mindestens die Komponente under Test muss deklariert werden

Angular Testing Utils: createComponent

  • TestBed.createComponent instanziiert die Komponente die getestet werden soll
  • Nach dem Erstellen der Komponente darf die TestBed Konfiguration nicht mehr verändert werden
  • die Methode gibt eine ComponentFixture zurück
  • über diese Fixture hat man Zugriff auf die Instanz der Komponente, fixture.componentInstance
  • und auf das DebugElement: fixture.debugElement

Angular Testing Utils: DebugElement

  • Mit dem DebugElement kann auf den DOM zugegriffen werden
  • mit debugElement.nativeElement auf ein HTMLElement
  • auf einem HTMLElement kann mit textContent der Text ausgelesen werden
  • mit debugElement.query(predicate) => DebugElement und queryAll(predicate) => DebugElement[] kann im DOM des Elements nach weiteren Elementen gesucht werden

ComponentFixture.detectChanges()

Die Change Detection im Test triggern

  • initial wird mit fixture.detectChanges() das initialisieren der Bindings getriggered
  • sämtliche Änderungen an der Komponente erfordern einen erneuten Aufruf - somit kann dies im Test explizit getriggert werden

Ein erstes Beispiel

                    
                        describe('MyComp', () => {
                            let component: MyComp;
                            let fixture: ComponentFixture<MyComp>;
                            beforeEach(() => {
                                TestBed.configureTestingModule({
                                    declarations: [MyComp]
                                });
                                fixture = TestBed.createComponent(MyComp);
                                component = fixture.componentInstance;
                            });
                            it('should work', () => {
                                let de: DebugElement = fixture.debugElement.query(
                                    By.css('h3'));
                                let el: HTMLElement = de.nativeElement;
                                component.title = 'my title';
                                fixture.detectChanges();
                                expect(el.textContent).toEqual('my title');
                            });
                        });
                    
                

Aufgabe 12.1 - Component Testing

  • Branch: 11_Forms_3
  • Lagere die h1 Überschrift in eine eigene Komponente aus
  • schreibe einen Test der überprüft, dass der Titel den korrekten Text enthält

Aufgabe 12.1 - Mögliche Lösung

  • Branch: 12_Testing_1
  • Wenn ihr eure Änderungen verwerfen möchtet und direkt zur Lösung wollt: git reset --hard && git checkout 12_Testing_1

Test Host Komponenten verwenden

  • verwendet man @Input() und @Output() so kann man Komponenten sehr gut mit einem Dummy Parent testen
  • Im Test inline eine Komponente erstellen
  • diese bindet die zu testende Komponente ein
  • beide müssen im declarations Array sein
  • queries wie gewohnt per CSS
  • die Dummy Host Komponente muss kreiert werden

Test Host Komponenten verwenden

                    
                        describe('Header Component with a host component', () => {

                            @Component({
                                template: `<comp [prop]="'dummy value'"></comp>`
                            })
                            class HostComponent {
                            }

                            let fixture: ComponentFixture<HostComponent>;

                            beforeEach(() => {
                                TestBed.configureTestingModule({
                                    declarations: [HostComponent, MyComponent],
                                });
                                fixture = TestBed.createComponent(HostComponent);
                            });
                            // ....
                            // eigentlicher Test Code
                        });
                    
                

Aufgabe 12.2 - Host Component Testing

  • Branch: 12_Testing_1
  • Gebe die Überschrift per @Input() in die Header Komponente
  • schreibe einen Test der eine Dummy Host Komponente nutzt und der Header Komponente eine Überschrift per [propertyBinding] übergibt

Aufgabe 12.2 - Mögliche Lösung

  • Branch: 12_Testing_2
  • Wenn ihr eure Änderungen verwerfen möchtet und direkt zur Lösung wollt: git reset --hard && git checkout 12_Testing_2

Testing Pipes

  • ... ist so einfach wie es nur geht ;-)
  • die Pipe im Test mit new instanziieren und die transform Funktion aufrufen
                    
                        import {KebabPipe} from "./kebab.pipe";

                        describe('KebabPipe', () => {

                            let pipe = new MyPipe();

                            it('should make camel to kebab"', () => {
                                expect(pipe.transform('aBcdE')).toBe('a-bcd-e');
                            });
                        });
                    
                

Aufgabe 12.3 - Testing Pipes

  • Branch: 12_Testing_2
  • Ihr ahnt es schon ;-)
  • schreibe einen Test für die phonenumber pipe
  • nutze die Nummer 0564410808 einmal ohne CountryCode und einmal mit und überprüfe die Resultate mit einer expectation

Aufgabe 12.3 - Mögliche Lösung

  • Branch: 12_Testing_3
  • Wenn ihr eure Änderungen verwerfen möchtet und direkt zur Lösung wollt: git reset --hard && git checkout 12_Testing_3

Testing Services

  • prinzipiell kann man einen Service ohne Dependency wie eine Pipe instanziieren und dann die Logik testen
  • interessant sind vor allem Services mit HTTP calls
  • Angular bietet hierfür das sog. in-memory-web-api-module an.
  • Ganz neu und daher leider noch relativ schlechte Doku.
  • Urspüngliche MockBackend Methode wurde deprecated.

Grundidee

  • Das Modul leitet alle Backend Calls um und gibt Daten aus der lokalen Config zurück.
  • Ein Beispiel findet Ihr auf dem Branch 12_Testing_4

Weiterführende Infos

  • Auf dem Branch 12_Testing_4 befindet sich auch ein Test mit Spys
  • weitere Infos zum Testing findet ihr in der Dokumentation

Ihr habt es geschafft!

Vielen Dank für die Teilnahme :-)