0 XP
?
Avancé50 min35 XP

Objectifs de cette leçon

  • Écrire des tests unitaires avec Jest
  • Tester les composants avec React Testing Library
  • Comprendre les tests d'intégration

Tests en React Native 🧪

Tester son code, c'est comme faire une révision avant un examen : ça permet d'être sûr que tout marche avant de publier !


1. Pourquoi tester ? 🤔

' Tests = filet de sécurité

' Sans tests :
' Tu changes une ligne → tu ne sais pas si tout le reste marche
' → Tu passes 1h à tout tester À LA MAIN

' Avec tests :
' Tu changes une ligne → tu lances npm test
' → En 30 secondes, TOUT est vérifié automatiquement
' → Si quelque chose casse, tu le sais IMMÉDIATEMENT
``'

---

## 2. Tests unitaires avec Jest 🃏

```text
' Test unitaire = tester une PETITE partie du code
' Une fonction, un composant, un helper...

' La règle d'or :
' Si une fonction a une "entrée" et une "sortie" → elle est testable !
' Ex : calculerPrixTTC(100) → 120
// Fonction utilitaire à tester
export function calculerPrixTTC(prixHT: number, tva: number = 0.2): number {
  return Math.round(prixHT * (1 + tva) * 100) / 100;
}

export function validerEmail(email: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

// TEST — fichier .test.ts
import { calculerPrixTTC, validerEmail } from "./utils";

describe("calculerPrixTTC", () => {
  it("calcule le prix TTC avec TVA 20%", () => {
    expect(calculerPrixTTC(100)).toBe(120);
  });

  it("arrondit à 2 décimales", () => {
    expect(calculerPrixTTC(99.99)).toBe(119.99);
  });

  it("gère la TVA personnalisée", () => {
    expect(calculerPrixTTC(100, 0.055)).toBe(105.5);  // TVA réduite
  });
});

describe("validerEmail", () => {
  it("accepte un email valide", () => {
    expect(validerEmail("user@exemple.com")).toBe(true);
  });

  it("rejette un email sans @", () => {
    expect(validerEmail("userexemple.com")).toBe(false);
  });

  it("rejette une chaîne vide", () => {
    expect(validerEmail("")).toBe(false);
  });
});
``'

---

## 3. Tests de composants avec RNTL 📱

```tsx
// Composant à tester
function BoutonConnexion({ onPress, desactive }: {
  onPress: () => void;
  desactive?: boolean;
}) {
  return (
    <TouchableOpacity
      onPress={onPress}
      disabled={desactive}
      testID="bouton-connexion"
    >
      <Text>Se connecter</Text>
    </TouchableOpacity>
  );
}

// TEST du composant
import { render, fireEvent } from "@testing-library/react-native";

describe("BoutonConnexion", () => {
  it("affiche le texte du bouton", () => {
    const { getByText } = render(
      <BoutonConnexion onPress={() => {}} />
    );
    expect(getByText("Se connecter")).toBeTruthy();
  });

  it("appelle onPress quand on clique", () => {
    const onPressMock = jest.fn();
    const { getByTestId } = render(
      <BoutonConnexion onPress={onPressMock} />
    );

    fireEvent.press(getByTestId("bouton-connexion"));
    expect(onPressMock).toHaveBeenCalledTimes(1);
  });

  it("désactive le bouton", () => {
    const { getByTestId } = render(
      <BoutonConnexion onPress={() => {}} desactive />
    );

    const bouton = getByTestId("bouton-connexion");
    expect(bouton.props.disabled).toBe(true);
  });
});
``'

---

## 4. Mock des API et AsyncStorage 🎭

```tsx
// Exemple : composant qui charge des données
function ListeUtilisateurs() {
  const [utilisateurs, setUtilisateurs] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch("https://api.exemple.com/users")
      .then((res) => res.json())
      .then((data) => {
        setUtilisateurs(data);
        setLoading(false);
      });
  }, []);

  if (loading) return <Text testID="loading">Chargement...</Text>;

  return (
    <FlatList
      data={utilisateurs}
      renderItem={({ item }) => <Text testID="user">{item.nom}</Text>}
    />
  );
}

// TEST
global.fetch = jest.fn(() =>
  Promise.resolve({
    json: () => Promise.resolve([{ id: 1, nom: "Alice" }, { id: 2, nom: "Bob" }]),
  })
) as jest.Mock;

describe("ListeUtilisateurs", () => {
  afterEach(() => jest.clearAllMocks());

  it("affiche le chargement puis les utilisateurs", async () => {
    const { getByTestId, findByTestId } = render(<ListeUtilisateurs />);

    // État de chargement
    expect(getByTestId("loading")).toBeTruthy();

    // Après le chargement
    const user = await findByTestId("user");
    expect(user).toBeTruthy();
  });
});
``'

---

## 5. Tests end-to-end (E2E) avec Detox 🎬

```text
' Tests E2E = simuler l'utilisation RÉELLE par un humain
' L'app est buildée et installée sur un simulateur
' Detox clique, swipe, saisit du texte... comme un vrai utilisateur !

' À tester en E2E :
' • Parcours de connexion complet
' • Ajout au panier jusqu'au paiement
' • Navigation entre écrans
' • Comportement hors-ligne
``'

```javascript
// Fichier de test Detox (.e2e.js)
describe("Parcours de connexion", () => {
  beforeAll(async () => {
    await device.launchApp();  // Lance l'app
  });

  it("permet de se connecter", async () => {
    // Saisir l'email
    await element(by.id("input-email")).typeText("user@exemple.com");
    // Saisir le mot de passe
    await element(by.id("input-password")).typeText("mon-mot-de-passe");
    // Appuyer sur le bouton
    await element(by.id("bouton-connexion")).tap();

    // Vérifier qu'on est bien connecté
    await expect(element(by.id("ecran-accueil"))).toBeVisible();
  });
});
``'

---

## Schéma récapitulatif 🧭

```mermaid
flowchart TD
    A["🧪 Pyramide des tests"] --> B["⬆️ E2E (Detox)<br/>Tests utilisateur<br/>Lent mais fiable"]
    A --> C["⬆️ Integration<br/>Composants + API<br/>React Native TL"]
    A --> D["⬇️ Unitaires (Jest)<br/>Fonctions + helpers<br/>Rapide et nombreux"]
``'

> **La pyramide des tests** : beaucoup de tests unitaires (rapides), quelques tests d'intégration (moyens), très peu de tests E2E (lents).

---

## Exercices pour toi 🎯

1. **Tests unitaires** : prends 3 fonctions utilitaires (formatage date, calcul TVA, validation email) et écris leurs tests Jest
2. **Tests composant** : crée un composant `FormulaireInscription` (email, password, bouton) et teste : rendu, validation, appel onPress
3. **Mock API** : crée un composant qui charge des données depuis une API et teste tous les états : loading, succès, erreur

Création des variables pas à pas

Prêt à explorer la mémoire

Appuie sur Play pour voir les variables se créer une par une

0/4

Visualisation d'exécution

ligne 3
code
1// 🧪 Test unitaire Jest
2test('calculerTTC ajoute 20%', () => {
3 expect(calculerTTC(100)).toBe(120);
4 expect(calculerTTC(0)).toBe(0);
5 expect(calculerTTC(1.5)).toBe(1.8);
6});
7
8// 🧩 Test intégration RNTL
9import { render, fireEvent } from '@testing-library/react-native';
10
11test('bouton appelle onPress', () => {
12 const onPress = jest.fn();
13 const { getByText } = render(<Button title="OK" onPress={onPress} />);
14 fireEvent.press(getByText('OK'));
15 expect(onPress).toHaveBeenCalledTimes(1);
16});
17
18// 🎬 Test E2E Detox
19// await element(by.id('email')).typeText('user@test.com');
20// await element(by.text('Connexion')).tap();
21// await expect(element(by.text('Bienvenue'))).toBeVisible();
Variables
Jest unitaire=expect().toBe()
RNTL intégration=render + fireEvent
Detox E2E=simulateur complet

🏗️ Pyramide des tests

E2E (Detox)Simule un vrai utilisateur~3 tests
Intégration (RNTL)Teste un composant isolé~20 tests
Unitaires (Jest)Teste une fonction pure~100 tests