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 3code
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