Une gestion d’erreur de son API .Net automatique via ses exceptions
...
Sommaire
Lorsqu’on réalise nos tests unitaires, le SUT (system under test) interagit avec différents objets dont le comportement n’est pas à tester.
On peut prendre l’exemple d’une méthode qui aurait pour but d’effectuer des calculs puis envoyer un mail via un service d’envoi: il n’y a pas de valeur ajoutée à initialiser ce service, on voudrait simplement vérifier que la méthode “sendMail” soit appelée.
On voudrait également ne pas avoir à initialiser des objets complexes lorsque ceux-ci ne sont pas nécessaires. C’est lorsqu’on cherche comment arriver à cette fin qu’on tombe sur des termes comme “Mock” (le plus connu) mais aussi Spy, Fake, Dummy etc… Tous ces termes désignent des objets destinés à remplacer les objets réels afin de réaliser des tests.
L’objectif de cet article est d’expliquer les différences entre ces différents termes pour pouvoir utiliser l’outil le plus adapté à vos besoins 🙂
Note: les exemples seront réalisés en Java avec le framework Mockito mais les concepts restent les mêmes indépendamment du langage/framework 🙂
Un Dummy est simplement une structure d’objet qui va permettre de satisfaire un constructeur.
Quand l’utiliser ?
Si je dois initialiser un objet qui prend différents objets dans son constructeur et que certains d’entre eux ne me seront d’aucune utilité pour les tests, je peux les remplacer par des dummies pour éviter une instanciation fastidieuse sans valeur ajoutée.
Exemple d’utilisation:
Admettons que je veuille tester une méthode qui ne manipule que les offers et le receipt de ma classe Store ci-dessous, je n’ai aucun intérêt à initialiser un SupermarketCatalog qui ne servira pas dans mes tests.
public Store(Receipt receipt, Offer offer, SupermarketCatalog catalog) { this.receipt = receipt; this.offer = offer; this.catalog = catalog; }
Par exemple si je veux tester:
public double myMethodToTest(){ return this.receipt.doSomething() + this.offer.doSomerhingElse(); }
Je n’ai pas besoin de catalogue comme il n’est pas utilisé dans cette méthode !
Avec Mockito je peux écrire:
SupermarketCatalog supermarketCatalogDummy = mock(SupermarketCatalog.class)
Cet objet permettra de remplir le contrat, c’est-à-dire qu’il fera office de SupermarketCatalog dans le constructeur mais il n’a pas d’implémentation.
Si on imagine que je possède des receipt et offers instanciés, je pourrais alors écrire:
Store myStore = new Store(receipt, offers, supermarketCatalogDummy)
Cela permet de ne pas avoir à initialiser d’objets complexes lorsqu’ils ne sont pas nécessaires à la portion que nous voulons tester !
Un ‘fake’ est un objet simplifié qui sera plus facile à utiliser pour les tests. On pourrait citer l’exemple le plus courant d’une implémentation simplifiée d’une persistance d’objets. Peut être que dans la réalité, vous persistez des informations sur AWS dans un S3 mais que pour vos tests, sauvegarder des informations dans un SQLite ou une base H2 serait suffisant: si vous créez une implémentation qui le permet: c’est un fake.
Exemple d’utilisation:
J’ai un catalogue qui sauvegarde des produits dans une base de données et j’en ai besoin pour mes tests. Je peux faire implémenter l’interface de ce catalogue à un fake et interagir avec une Hashmap plutôt qu’une base de données réelle.
package dojo.supermarket.model; import java.util.HashMap; import java.util.Map; public class FakeCatalog implements SupermarketCatalog { private Map<String, Product> products = new HashMap<>(); private Map<String, Double> prices = new HashMap<>(); @Override public void addProduct(Product product, double price) { this.products.put(product.getName(), product); this.prices.put(product.getName(), price); } @Override public double getUnitPrice(Product p) { return this.prices.get(p.getName()); } }
Les “stubs” se substituent aux appels de méthode afin de donner des réponses pré-faites à des appels
Exemple d’utilisation:
Lorsque la portion que vous voulez tester doit obtenir des informations provenant de l’extérieur (via un service, une base de données etc..), vous voulez que cette information soit toujours la même indépendamment de l’état réel du service extérieur (BDD down par exemple).
when(ProductRepository.getProduct(1)).thenReturn(new Product(1,'toothBrush'));
Note: les Spies sont des stubs qui peuvent enregistrer leur état mais nous ne le détaillerons pas dans cet article.
Les mocks se substituent aux appels de méthodes pour faire des vérifications: on peut par exemple vérifier qu’une méthode a été appelée sans pour autant l’exécuter.
Exemple de cas d’utilisation:
Lorsque votre méthode appelle une API externe qui va se charger de persister un objet par exemple, vous pouvez avoir envie de vérifier que cette méthode soit appelée.
verify(ProductAPIService).save('toothBruch')
Mon explication c’est que lorsqu’on utilise un framework de test, on ne parle que de Mock: Mockito, EasyMock, Powermock…
En plus ces objets “Mock” peuvent endosser le rôle de Dummy, Stub, Mock.. Donc Mock est devenu dans le langage courant, un terme qui définit toutes les doublures de tests.
D’ailleurs dans l’exemple sur les Dummy, pour le créer on a utilisé la ligne de code suivante :
SupermarketCatalog supermarketCatalogDummy = mock(SupermarketCatalog.class)
Cet article n’est qu’une porte d’entrée vers les doublures de tests, il est important d’avoir une idée (même simplifiée) de ces différents termes pour aller plus loin dans l’étude de celles-ci et d’améliorer leur utilisation dans notre quotidien.
Maintenant que vous avez lu cet article, je vous recommande d’aller lire sans plus attendre notre article “Dans quelles conditions utiliser des Mocks dans ses tests?” et spoil, pas aussi souvent qu’on pourrait le penser! 😀
Si vous souhaitez aller plus loin, je vous conseille de jeter un œil aux sources de cet article.
https://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs
https://stackoverflow.com/questions/346372/whats-the-difference-between-faking-mocking-and-stubbing
https://dzone.com/articles/test-doubles-mockito
https://www.youtube.com/watch?v=pKBjufM024U&t=2095s
Image d’illustration: https://unsplash.com/photos/ufgOEVZuHgM
On va voir comment avoir en quelques minutes des assertions qui vont vérifier les endpoints de notre API avec des scénarios de ce genre: Feature: Create a new account As a visitor, I can create an account to access the game Scenario: A visitor c...
Depuis .NET 9, le le support d’OpenAPI est directement inclus dans .NET et ne passe plus par les librairies Swagger par défaut (plus d’info sur ce choix ici si jamais ça vous intéresse). De façons simplifiée, la librairie Swashbuckle.AspNetCore.Sw...
Description du problème Par défaut, il n’est pas autorisé de faire des requêtes entre une application qui est dans un domaine A vers une autre qui serait dans un domaine B (pour des raisons de sécurité, il y a plus de détails dans les sources). S...