Outils pour utilisateurs

Outils du site


web:javascript:angular:unittests

Ceci est une ancienne révision du document !


Unit testing in Angular

Testing Overview

End to End

  • Live running application
  • Tests exercise live application
  • Automated
  • Validate application as a hole

Unit testing

  • Single unit of code (often a class)

Integration and functional testing

  • More than a unit, less than the complete application
  • Check that a part is working with another part

Mocking

Permet d'isoler le code que l'on veut tester.

Types de mocks:

  • Dummies
  • Stubs
  • Spies
  • True mocks

Unit Tests in Angular

Types de tests unitaires dans Angular

  • Isolated
  • Integration (création de module)
    • Shallow
    • Deep

Tools of Unit Testing with Angular

  • Karma
  • Jasmin

Autres

  • Jest
  • Mocha/Chai
  • Sinon
  • TestDouble
  • Wallaby
  • Cypress
  • End to end tools

Installing and running

Écrire le premier test unitaire

Exemple de test de base:

describe('my first test', () => {
  let sut; // system under test

  beforeEach(() => {
    sut = {};
  
  });

  it('should be true if true', () => {
    // arrange
    sut.a = false;
    
    // act
    sut.a = true;
    
    //assert
    expect(true).toBe(true);
  });
});

Exécuter les tests unitaires

Par défaut, avec un projet Angular:

$ npm test

Écrire de bons tests unitaires

Structure:

  • Arrange all necessary preconditions and inputs
  • Act on the object or class under test
  • Assert that the expected result have occured

DAMP vs DRY:

  • DRY (don't repeat yourself) → remove duplication
  • DAMP → Repeat yourself if necessary

Tell the story:

  • A test should be a complete story, all within the it()
  • You shouldn't need to look around much to understant the test
  • Techniques
    • Move less interesting setup to beforeEach()
    • Keep critical setup within the it()
    • Include Arrange, Act, and Assert inside the it()

Isolated Unit Tests

Tester un Pipe

Exemple de pipe:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'strength'
})
export class StrengthPipe implements PipeTransform {
  transform(value: number): string {
    if(value < 10) {
      return value + " (weak)";
    } else if(value >= 10 && value < 20) {
      return value + " (strong)";
    } else {
      return value + " (unbelievable)";
    }
  }
}

Le test:

import { StrengthPipe } from "./strength.pipe";

describe('StrengthPipe', () => {
  it('should display weak if strength is 5', () => {
    let pipe = new StrengthPipe();

    expect(pipe.transform(5)).toEqual('5 (weak)');
  })

  it('should display strong if strength is 10', () => {
    let pipe = new StrengthPipe();

    expect(pipe.transform(10)).toEqual('10 (strong)');
  })
})

Tester un service

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

@Injectable()
export class MessageService {
  messages: string[] = [];

  add(message: string) {
    this.messages.push(message);
  }

  clear() {
    this.messages = [];
  }
}
import { MessageService } from "./message.service";
 
describe('MessageService', () => {
  let service: MessageService;
 
  beforeEach(() => {
 
  });
 
  it('should have no messages to start', () => {
    service = new MessageService();
 
    expect(service.messages.length).toBe(0);
  });
 
  it('should add a message when add is called', () => {
    service = new MessageService();
 
    service.add('message1');
 
    expect(service.messages.length).toBe(1);
  });
 
  it('should remove all messages when clear is called', () => {
    service = new MessageService();
    service.add('message1');
 
    service.clear();
 
    expect(service.messages.length).toBe(0);
  });
 
});

Tester un Component

import { Component, OnInit } from '@angular/core';
 
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
 
@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
  heroes: Hero[];
 
  constructor(private heroService: HeroService) { }
 
  ngOnInit() {
    this.getHeroes();
  }
 
  getHeroes(): void {
    this.heroService.getHeroes()
    .subscribe(heroes => this.heroes = heroes);
  }
 
  add(name: string): void {
    name = name.trim();
    var strength = 11
    if (!name) { return; }
    this.heroService.addHero({ name, strength } as Hero)
      .subscribe(hero => {
        this.heroes.push(hero);
      });
  }
 
  delete(hero: Hero): void {
    this.heroes = this.heroes.filter(h => h !== hero);
    this.heroService.deleteHero(hero).subscribe();
  }
 
}
import { HeroesComponent } from "./heroes.component";
import { of } from "rxjs";
 
describe('HeroesComponent', () => {
  let component: HeroesComponent;
  let HEROES;
  let mockHeroService;
 
  beforeEach(() => {
    HEROES = [
      {id:1, name: 'SpiderDude', strength: 8},
      {id:2, name: 'Wonderful Woman', strength: 24},
      {id:3, name: 'SuperDude', strength: 55}
    ]
 
    mockHeroService = jasmine.createSpyObj(['getHeroes', 'addHero', 'deleteHero'])
 
    component = new HeroesComponent(mockHeroService);
  })
 
  describe('delete', () => {
 
    it('should remove the indicated hero from the heroes list', () => {
      mockHeroService.deleteHero.and.returnValue(of(true))
      component.heroes = HEROES;
 
      component.delete(HEROES[2]);
 
      expect(component.heroes.length).toBe(2);
    })
 
    it('should call deleteHero', () => {
      mockHeroService.deleteHero.and.returnValue(of(true))
      component.heroes = HEROES;
 
      component.delete(HEROES[2]);
 
      expect(mockHeroService.deleteHero).toHaveBeenCalledWith(HEROES[2]);
    })
  })
})

Tester

Shallow Integration Tests

Using NO_ERRORS_SCHEMA

import {NO_ERRORS_SCHEMA} from '@angular/core';
TestBed.configureTestingModule({
  declarations: [WriteUsDialogComponent],
  imports: [],
  providers: [],
  schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();

Testing Rendered HTML

component.client = {firstName: 'John', lastName: 'Doe'};

fixture.detectChanges();

expect(fixture.nativeElement.querySelector('a').textContent).toContain('John');

NativeElement vs DebugElement

DebugElement est un wrapper sur NativeElement qui a plusieurs fonctionnalités semblable au NativeElement.

expect(fixture.debugElement.query(By.css('a')).nativeElement.textContent).toContain('John');
const de = fixture.debugElement.query(By.css('a')); // de = debugElement
expect(de.nativeElement.textContent).toContain('John');

DebugElement permet d'avoir accès aux directives, par exemple routerLink.

More complex Shallow Tests

Mocking an injected Service

Pour les tests de component qui ont besoin de services, on peut utiliser le TestBed.

describe('HeroComponent (shallow tests)', () => {
  let fixture: ComponentFixture<HeroComponent>;  // le fixture est un wrapper sur le component à tester
 
  let mockHeroService;
 
  beforeEach(() => {
 
    HEROES = [
      {id:1, name: 'SpiderDude', strength: 8},
      {id:2, name: 'Wonderful Woman', strength: 24},
      {id:3, name: 'SuperDude', strength: 55}
    ];
 
    mockHeroService = jasmine.createSpyObj(['getHeroes', 'addHero', 'deleteHero']);
 
    TestBed.configureTestingModule({
      declarations: [HeroComponent],
      providers: [{provide: HeroService, useValue: mockHeroService}],
      schemas: [NO_ERRORS_SCHEMA],
    });
 
    fixture = TestBed.createComponent(HeroComponent);
  });
 
  it('should have the correct hero', () => {
    fixture.componentInstance.hero = {id: 1, name: 'SuperDude', strenght: 3};
 
    expect(fixture,componentInstance.
  });
 
  it('should set heroes correctly from the service', () => {
    mockHeroService.getHeroes.and.returnValue(of(HEROES))
    fixture.detectChanges();
 
    expect(fixture.componentInstance.heroes.length).toBe(3);
  });
});

Pour info, getHeroes() appelle getHeroes() du service qui est un observable:

getHeroes(): void {
  this.heroService.getHeroes()
  .subscribe(heroes => this.heroes = heroes);
}

Dealing with Lists of Elements

La mention

web/javascript/angular/unittests.1560204069.txt.gz · Dernière modification : 2022/02/02 00:43 (modification externe)