Découvrez les nouveautés d'Angular 20 en quelques minutes

Angular 20 arrive avec plusieurs nouveautés et stabilisation des API: Zoneless, les APIs resource() et httpResource(), un nouveau runner de tests, etc. La vidéo vous donne un aperçu de ces nouveautés.

Abonnez-vous à notre chaîne

Pour profiter des prochaines vidéos sur Angular, abonnez-vous à la nouvelle chaîne YouTube !

Skip to content

Vous souhaitez recevoir de l'aide sur ce sujet ? rejoignez la communauté Angular.fr sur Discord.

fakeAsync et tick : Tester le code asynchrone en Angular

Dans Angular, beaucoup de fonctionnalités reposent sur l'asynchrone : les Observables, les Promises, les timers... Tester ce code peut être complexe car il faut attendre que les opérations se terminent. fakeAsync et tick sont des outils Angular qui permettent de contrôler le temps dans vos tests pour les rendre plus rapides et prévisibles.

🕐 Analogie du quotidien

Imaginez que vous testez un minuteur de cuisine. Normalement, vous devriez attendre 3 minutes pour voir si l'alarme se déclenche. Mais avec fakeAsync et tick, c'est comme si vous aviez une télécommande magique qui vous permet d'accélérer le temps : vous pouvez faire défiler 3 minutes en quelques millisecondes !

🎯 Pourquoi utiliser fakeAsync et tick ?

En Angular, vous rencontrez souvent du code asynchrone :

  • Observables avec des délais
  • setTimeout et setInterval
  • Promises avec des délais
  • async pipe dans les templates

Sans fakeAsync, vos tests devraient attendre le temps réel, ce qui les rend lents et peu fiables.

🔧 Les bases de fakeAsync et tick

fakeAsync : Le contrôleur de temps

fakeAsync encapsule votre test dans un environnement où le temps est contrôlé. Angular remplace l'horloge système par une horloge virtuelle.

tick : L'accélérateur de temps

tick() fait avancer l'horloge virtuelle d'un nombre de millisecondes spécifié.

📝 Exemple simple avec setTimeout

Voici comment tester un code qui utilise setTimeout :

typescript
import { fakeAsync, tick } from '@angular/core/testing';

it('devrait mettre à jour la valeur après 1 seconde', fakeAsync(() => {
  let valeur = 0;

  // Simule une opération qui prend 1 seconde
  setTimeout(() => {
    valeur = 42;
  }, 1000);

  // Au début, la valeur n'a pas encore changé
  expect(valeur).toBe(0);

  // On avance le temps de 1000ms instantanément
  tick(1000);

  // Maintenant la valeur a été mise à jour
  expect(valeur).toBe(42);
}));

🚀 Exemple avec un Observable

Testons un service qui retourne des données avec un délai :

typescript
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { fakeAsync, tick } from '@angular/core/testing';

it('devrait retourner les utilisateurs après 500ms', fakeAsync(() => {
  let resultat: User[] | undefined;

  // Simule un appel API avec délai
  of([
    { id: 1, name: 'Alice', username: 'alice', email: '[email protected]' },
    { id: 2, name: 'Bob', username: 'bob', email: '[email protected]' }
  ]).pipe(delay(500)).subscribe(users => resultat = users);

  // Au début, pas encore de résultat
  expect(resultat).toBeUndefined();

  // On avance le temps de 500ms
  tick(500);

  // Maintenant on a les utilisateurs
  expect(resultat).toHaveLength(2);
  expect(resultat![0].name).toBe('Alice');
}));

🧪 Exemple complet avec un composant

Créons un composant qui charge des utilisateurs avec un délai :

typescript
import { Component, signal } from '@angular/core';
import { User } from './user.interface';

@Component({
  selector: 'app-user-list',
  template: `
    @if (loading()) {
      <p>Chargement...</p>
    } @else {
      @for (user of users(); track user.id) {
        <div>{{ user.name }}</div>
      }
    }
  `
})
export class UserListComponent {
  users = signal<User[]>([]);
  loading = signal(true);

  constructor() {
    this.loadUsers();
  }

  // Simule le chargement des utilisateurs avec un délai
  private loadUsers(): void {
    setTimeout(() => {
      this.users.set([
        { id: 1, name: 'Alice', username: 'alice', email: '[email protected]' },
        { id: 2, name: 'Bob', username: 'bob', email: '[email protected]' }
      ]);
      this.loading.set(false);
    }, 1000);
  }
}

Testons ce composant :

typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { fakeAsync, tick } from '@angular/core/testing';
import { UserListComponent } from './user-list.component';

describe('UserListComponent', () => {
  let component: UserListComponent;
  let fixture: ComponentFixture<UserListComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [UserListComponent]
    });
    fixture = TestBed.createComponent(UserListComponent);
    component = fixture.componentInstance;
  });

  it('devrait afficher les utilisateurs après le chargement', fakeAsync(() => {
    // Au début, on est en mode chargement
    expect(component.loading()).toBe(true);
    expect(component.users()).toHaveLength(0);

    // On avance le temps de 1000ms
    tick(1000);

    // Maintenant les utilisateurs sont chargés
    expect(component.loading()).toBe(false);
    expect(component.users()).toHaveLength(2);
    expect(component.users()[0].name).toBe('Alice');
  }));
});

⚡ tick() sans paramètre

Vous pouvez utiliser tick() sans paramètre pour avancer le temps jusqu'à ce que toutes les tâches en attente soient terminées :

typescript
it('devrait traiter toutes les tâches en attente', fakeAsync(() => {
  let compteur = 0;

  setTimeout(() => compteur++, 100);
  setTimeout(() => compteur++, 200);
  setTimeout(() => compteur++, 300);

  // Avance le temps jusqu'à ce que toutes les tâches soient terminées
  tick();

  expect(compteur).toBe(3);
}));

🎯 Cas d'usage avancés

Tester des animations

typescript
it('devrait animer l\'apparition d\'un élément', fakeAsync(() => {
  const element = document.createElement('div');
  element.style.transition = 'opacity 0.5s';
  element.style.opacity = '0';

  // Déclenche l'animation
  element.style.opacity = '1';

  // Avance le temps de la durée de l'animation
  tick(500);

  // Vérifie que l'animation est terminée
  expect(element.style.opacity).toBe('1');
}));

Tester des retry avec backoff

typescript
it('devrait retry avec un délai exponentiel', fakeAsync(() => {
  let tentatives = 0;
  const maxTentatives = 3;

  function essayer(): void {
    tentatives++;
    if (tentatives < maxTentatives) {
      setTimeout(() => essayer(), Math.pow(2, tentatives) * 100);
    }
  }

  essayer();

  // Avance le temps pour toutes les tentatives
  tick(100 + 200 + 400); // 700ms au total

  expect(tentatives).toBe(3);
}));

⚠️ Bonnes pratiques

Règles importantes

  • Utilisez fakeAsync uniquement pour tester du code asynchrone
  • N'oubliez pas d'appeler tick() pour avancer le temps
  • Utilisez tick() sans paramètre pour traiter toutes les tâches en attente
  • Testez les cas d'erreur avec des délais

Attention

  • tick() ne fonctionne que dans un contexte fakeAsync
  • Ne mélangez pas fakeAsync avec async/await dans le même test
  • Certaines APIs natives (comme fetch) ne sont pas interceptées par fakeAsync

🔍 Dépannage courant

Erreur : "tick is called outside of fakeAsync"

typescript
// ❌ Incorrect
it('test', () => {
  tick(1000); // Erreur !
});

// ✅ Correct
it('test', fakeAsync(() => {
  tick(1000); // OK !
}));

Tester des Promises

typescript
it('devrait résoudre une Promise avec délai', fakeAsync(() => {
  let resultat: string | undefined;

  new Promise<string>(resolve => {
    setTimeout(() => resolve('Succès'), 1000);
  }).then(valeur => resultat = valeur);

  expect(resultat).toBeUndefined();
  
  tick(1000);
  
  expect(resultat).toBe('Succès');
}));