Test et simulation des applications Dart
Sommaire
- Structurer le code pour la testabilité
- Gérer les dépendances
- Générer des mocks
- Implémenter les tests unitaires
- Flux de travail : créer et exécuter des tests simulés
- Exemples
Structurer le code pour la testabilité
Concevez les classes Dart pour supporter l'injection de dépendances. Isolez les dépendances externes complexes (comme les clients API ou les bases de données) afin qu'elles puissent être remplacées par des objets mock lors des tests.
- Injectez les services externes (par ex.,
http.Client) via les constructeurs de classe. - Représentez les URLs strictement comme des objets
Urien utilisantUri.parse(string). - Utilisez les fonctionnalités orientées objet de Dart (classes, mixins) pour définir des interfaces claires pour les interactions externes.
Gérer les dépendances
Configurez le fichier pubspec.yaml avec les paquets nécessaires de test et de génération de code.
- Ajoutez les dépendances runtime (par ex.,
package:http) en utilisantdart pub add http. - Ajoutez les dépendances de test en utilisant
dart pub add dev:test dev:mockito dev:build_runner. - Importez les bibliothèques HTTP avec un préfixe pour éviter les collisions d'espace de noms :
import 'package:http/http.dart' as http;.
Générer des mocks
Utilisez package:mockito et build_runner pour générer automatiquement des classes mock pour des scénarios fixes et la vérification du comportement.
- Utilisez toujours l'annotation
@GenerateNiceMocks(préférable à@GenerateMockspour éviter les exceptions de stub manquant). - Placez l'annotation dans le fichier de test en passant une liste d'objets
MockSpec<Type>(). - Importez le fichier généré en utilisant l'extension
.mocks.dart. - Exécutez
build_runnerpour générer les fichiers mock :dart run build_runner build.
Implémenter les tests unitaires
Isolez le système en test en utilisant les objets mock générés. Utilisez package:test pour structurer la suite de test.
- Stubbing : Configurez le comportement du mock avant d'interagir avec le système en test.
- Utilisez
when(mock.method()).thenReturn(value)pour les méthodes synchrones. - CRITIQUE : Utilisez toujours
thenAnswer((_) async => value)pour les méthodes retournant unFutureou unStream. Ne utilisez jamaisthenReturnpour les retours asynchrones.
- Utilisez
- Vérification : Vérifiez que le système en test a interagi correctement avec l'objet mock.
- Utilisez
verify(mock.method()).called(1)pour vérifier les compteurs d'invocation exacts. - Utilisez des argument matchers comme
any,anyNamed, oucaptureAnypour une vérification flexible.
- Utilisez
Flux de travail : créer et exécuter des tests simulés
Utilisez la liste de contrôle suivante pour implémenter et vérifier des tests unitaires simulés.
Progression des tâches
- [ ] 1. Identifiez la dépendance externe à simuler (par ex.,
http.Client). - [ ] 2. Injectez la dépendance dans le constructeur de la classe cible.
- [ ] 3. Créez un fichier de test (par ex.,
target_test.dart) et ajoutez@GenerateNiceMocks([MockSpec<Dependency>()]). - [ ] 4. Ajoutez la directive
partouimportpour le fichier.mocks.dartgénéré. - [ ] 5. Exécutez
dart run build_runner buildpour générer les classes mock. - [ ] 6. Écrivez les cas de test en utilisant
group()ettest(). - [ ] 7. Stubifiez les comportements requis en utilisant
when(). - [ ] 8. Exécutez la méthode cible.
- [ ] 9. Vérifiez les interactions en utilisant
verify()et vérifiez les résultats en utilisantexpect(). - [ ] 10. Exécutez la suite de test en utilisant
dart test.
Boucle de rétroaction : échecs de tests
Si les tests échouent ou que build_runner rencontre des erreurs :
- Exécutez le validateur : Exécutez
dart testoudart run build_runner build. - Examinez les erreurs : Vérifiez les stubs manquants, les argument matchers incompatibles, ou les erreurs de syntaxe dans les fichiers générés.
- Corrigez :
- Si une méthode mock lève une erreur null inattendue, assurez-vous que vous avez utilisé
@GenerateNiceMocks. - Si un stub asynchrone lève un
ArgumentError, changezthenReturnenthenAnswer. - Si
build_runneréchoue, assurez-vous que l'import.mocks.dartcorrespond exactement au nom du fichier.
- Si une méthode mock lève une erreur null inattendue, assurez-vous que vous avez utilisé
- Répétez jusqu'à ce que tous les tests réussissent.
Exemples
Exemple de test et simulation haute fidélité
1. Système en test (lib/api_service.dart)
import 'dart:convert';
import 'package:http/http.dart' as http;
class ApiService {
final http.Client client;
ApiService(this.client);
Future<String> fetchData(String urlString) async {
final uri = Uri.parse(urlString);
final response = await client.get(uri);
if (response.statusCode == 200) {
return jsonDecode(response.body)['data'];
} else {
throw Exception('Failed to load data');
}
}
}
2. Implémentation de test (test/api_service_test.dart)
import 'package:test/test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:http/http.dart' as http;
import 'package:my_app/api_service.dart';
// Generate the mock class for http.Client
@GenerateNiceMocks([MockSpec<http.Client>()])
import 'api_service_test.mocks.dart';
void main() {
group('ApiService', () {
late ApiService apiService;
late MockClient mockHttpClient;
setUp(() {
mockHttpClient = MockClient();
apiService = ApiService(mockHttpClient);
});
test('returns data if the http call completes successfully', () async {
// Arrange: Stub the async HTTP GET request using thenAnswer
when(mockHttpClient.get(any)).thenAnswer(
(_) async => http.Response('{"data": "Success"}', 200),
);
// Act
final result = await apiService.fetchData('https://api.example.com/data');
// Assert
expect(result, 'Success');
// Verify the mock was called with the correct Uri
verify(mockHttpClient.get(Uri.parse('https://api.example.com/data'))).called(1);
});
test('throws an exception if the http call completes with an error', () {
// Arrange
when(mockHttpClient.get(any)).thenAnswer(
(_) async => http.Response('Not Found', 404),
);
// Act & Assert
expect(
apiService.fetchData('https://api.example.com/data'),
throwsException,
);
});
});
}