Tests unitaires d'un DomainService WCF RIA

Partie 3 - La DomainServiceTestHost

Kyle McClellan

Traducteur

Deepin Prayag

Étant donné que WCF RIA Services emploie un « pipeline pattern » pour invoquer vos opérations DomainService, il n'est pas toujours évident de savoir comment les tester. Dans cette série d'articles nous allons voir un petit DomainService et comment le tester. Entre autres, nous allons voir comment implémenter une IDomainServiceFactory personnalisée, comment implémenter le pattern Repository, et comment utiliser la DomainServiceTestHost.

Langage/Outil/Technologie : C#, WCF RIA Services

Commentez cet article : Commentez

- Titre : Tests unitaires d'un DomainService WCF RIA

- Auteur : Kyle McClellan

- Traducteur : Deepin Prayag

- Parution : 10 décembre 2012

- Licence : Copyright ® 2012 Kyle McClellan. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Traduction

- Introduction

- Le DomainService

- La DomainServiceTestHost

- Tester un DomainService

- Tester la validation et l'autorisation

- Conclusion

- Une note sur la DomainServiceTestHost

- Liens

- Remerciements

Traduction

Cet article est la traduction la plus fidèle possible de l'article original de Kyle McClellan, Unit Testing a WCF RIA DomainService: Part 3, The DomainServiceTestHost.

Introduction

Dans cette conclusion excitante de ma série sur les tests unitaires, je vais vous montrer comment utiliser la DomainServiceTestHost pour tester vos DomainService. Dans la première partie et la deuxième partie de cette série, je vous ai montré comment extraire les dépendances externes en utilisant l'IDomainServiceFactory et comment utiliser le pattern Repository. Maintenant que tout le travail de fond a été effectué, je vais vous montrer comment faire pour tester votre logique métier.

Le DomainService

Le DomainService que nous testons ressemble à ceci :

1. public class BookClubDomainService : RepositoryDomainService

2. {

3. public BookClubDomainService(

4. IUnitOfWork unitOfWork,

5. IBookRepository bookRepository,

6. ILibraryService libraryService,

7. IApprovalSystem approvalSystem) { ... }

9. // Test 1: Operation should return all books

10. // Test 2: Operation should return books with categories

11. // Test 3: Operation should return books ordered by BookID

12. public IQueryable<Book> GetBooks() { ... }

14. // Test 1: Operation should return all books for category

15. // Test 2: Operation should return books ordered by BookID

16. public IQueryable<Book> GetBooksForCategory(int categoryId) { ... }

18. // Test 1: Operation should insert book

19. // Test 2: Operation should set the added date

20. // Test 3: Operation should request approval for book with invalid ASINs

21. // Test 4: Operation should request approval for book not yet published

22. public void InsertBook(Book book) { ... }

24. // Test 1: Operation should update book

25. // Test 2: Operation should return validation errors

26. public void UpdateBook(Book book) { ... }

28. // Test 1: Operation should update book

29. // Test 2: Operation should update the added date

30. [Update(UsingCustomMethod = true)]

31. public void AddNewEdition(Book book) { ... }

33. // Test 1: Operation should delete book

34. // Test 2: Operation should require authentication

35. [RequiresAuthentication]

36. public void DeleteBook(Book book) { ... }

38. // Test 1: Operation should return the most recent added date

39. public DateTime GetLatestActivity() { ... }

40. }

Il possède les opérations Query, Insert, Update et Delete standard, en plus des opérations Query, Update, et Invoke (/ Service) personnalisées. Le constructeur accepte un certain nombre de paramètres, chacun représentant une dépendance externe dans le code. J'ai marqué chaque méthode avec les tests que nous allons écrire contre celle-ci afin de ne pas avoir à connaître les détails de l'implémentation (ils sont dans l'exemple, je fais juste l'impasse dessus dans cet article).

La DomainServiceTestHost

La DomainServiceTestHost est une nouvelle classe au sein de l'assembly Microsoft.ServiceModel.DomainServices.Server.UnitTesting (maintenant disponible sur NuGet et bientôt livrée avec le « Toolkit »). Elle est conçue pour vous aider à tester les opérations DomainService individuelles. L'API se rapproche particulièrement des conventions DomainService (et peut aider à les clarifier si vous êtes toujours un peu dans le flou). À titre d'exemple, l'hôte de test possède une méthode Query pour récupérer des données, et des méthodes Insert, Update et Delete pour les modifier.

Outre la prise en charge des opérations standard, l'hôte de test simplifie le test de la validation et de l'autorisation. Pour chaque signature standard dans la DomainServiceTestHost, il existe une variante TryXx qui facilite la capture des erreurs de validation. Par exemple, Query est associée à TryQuery et Insert à TryInsert. Aussi, à chaque fois que vous créez un hôte de test, vous pouvez passer un IPrincipal dans le constructeur avec lequel les opérations doivent être exécutées. Cela facilite le fait de parcourir un nombre d'utilisateurs de test pendant que vous validez vos métadonnées d'autorisation. Enfin, l'hôte de test peut être créé avec une méthode de fabrique qu'il utilise pour instancier un DomainService. Cela vous permet d'initialiser un DomainService avec des dépendances spécifiques aux tests.

Tester un DomainService

Enfin, nous arrivons à la partie intéressante. Dans cette section, je vais vous guider à travers les étapes nécessaires pour tester votre logique métier. D'abord, nous allons initialiser les variables locales que nous allons utiliser avec chaque test.

1. [TestInitialize]

2. public void TestInitialize()

3. {

4. this._libraryService = new MockLibraryService();

5. this._approvalSystem = new FakeApprovalSystem();

6. this._unitOfWork = new FakeUnitOfWork();

7. this._bookRepository = new MockBookRepository();

9. this._domainServiceTestHost =

10. new DomainServiceTestHost<BookClubDomainService>(

11. this.CreateDomainService);

12. }

14. private BookClubDomainService CreateDomainService()

15. {

16. return new BookClubDomainService(

17. this._unitOfWork,

18. this._bookRepository,

19. this._libraryService,

20. this._approvalSystem);

21. }

Comme vous pouvez le voir, j'ai écrit des types mock/objet factice/bouchon simples pour chaque dépendance. J'avais discuté un peu de MockBookRepository dans mon article précédent. Dans le contexte des tests suivants, il est important de souligner que j'ai initialisé le repository avec un jeu initial de données. Les trois autres sont de simples implémentations de tests. Aussi, j'ai fourni une méthode CreateDomainService que je peux passer à l'hôte de test qui initialise ma BookClubDomainService avec les dépendances de tests. Si vous n'êtes pas familier avec les tests de Visual Studio, la méthode [TestInitialize] sera appelée avant le début de chaque test.

En démarrant avec quelque chose de simple, nous allons jeter un œil au test de la requête par défaut.

1. [TestMethod]

2. [Description("Tests that the GetBooks query returns all the books")]

3. public void GetBooks_ReturnsAllBooks()

4. {

5. IEnumerable<Book> books = this._domainServiceTestHost.

6. Query(ds => ds.GetBooks());

8. Assert.AreEqual(

9. this._bookRepository.GetBooksWithCategories().Count(), books.Count(),

10. "Operation should return all books");

11. }

Dans cette méthode, nous demandons à l'hôte de test de renvoyer les résultats de la requête. Le paramètre « ds » dans l'expression lambda est l'instance du DomainService que nous testons. Comme l'IntelliSense pour l'opération de requête est un peu détaillé (comme c'est le cas dès que le type Expression se manifeste), je vais vous donner un autre exemple de sorte que vous vous y habituiez. Cette fois, nous testons la requête personnalisée.

1. [TestMethod]

2. [Description(

3. "Tests that the GetBooksForCategory query orders books by BookID")]

4. public void GetBooksForCategory_OrderedByBookID()

5. {

6. int categoryId = this._bookRepository.GetTable<Category>().

7. First().CategoryID;

9. IEnumerable<Book> books = this._domainServiceTestHost.Query(

10. ds => ds.GetBooksForCategory(categoryId));

12. Assert.IsTrue(books.OrderBy(b => b.BookID).SequenceEqual(books),

13. "Operation should return books ordered by BookID");

14. }

Dans cet extrait nous passons une variable locale à l'opération de requête, mais tout le reste est à peu près le même. Nous avons reçu la collection renvoyée et maintenant nous vérifions qu'elle est dans un ordre trié.

Les tests pour les opérations Insert, Update, Delete sont tout aussi faciles à écrire. Le simple fait d'appeler la méthode sur l'hôte redirigera vers l'opération correspondante dans votre DomainService.

1. [TestMethod]

2. [Description("Tests that the InsertBook operation inserts a new book")]

3. public void InsertBook_InsertsNewBook()

4. {

5. int categoryId = this._bookRepository.GetTable<Category>().

6. First().CategoryID;

8. Book book = new Book

9. {

10. ASIN = "1234567890",

11. Author = "Author",

12. CategoryID = categoryId,

13. Description = "Description",

14. PublishDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)),

15. Title = "Title",

16. };

18. this._domainServiceTestHost.Insert(book);

20. Assert.IsTrue(book.BookID > 0,

21. "New book should have a valid BookID");

23. Book addedBook = this._bookRepository.GetEntities().

24. Single(b => b.BookID == book.BookID);

26. Assert.IsNotNull(addedBook,

27. "Operation should insert book");

28. }

Pour reformuler ce que j'ai dit ci-dessus, appeler Insert sur l'hôte de test avec un Book fait un appel à notre opération DomainService, InsertBook. Comme vous pouvez le voir, à la fin de ce test notre nouveau livre a été ajouté au repository.

En plus des méthodes Query, Insert, Update et Delete, des opérations Named Updates et Invoke sont également prises en charge. Dans les deux cas, la syntaxe est très similaire au test d'une Query.

1. this._domainServiceTestHost.Update(

2. ds => ds.AddNewEdition(book), original);

3. DateTime result = this._domainServiceTestHost.Invoke(

4. ds => ds.GetLatestActivity());

AddNewEdition met à jour le livre et exécute une logique métier personnalisée et GetLatestActivity renvoie une DateTime liée au tout dernier livre. Encore une fois, le paramètre « ds » dans l'expression lambda fait référence au DomainService en train d'être testé.

Tester la validation et l'autorisation

Tester les métadonnées de validation et d'autorisation pour une opération DomainService peut être tout aussi important que tester la logique métier. Une suite de tests unitaires vérifiant l'autorisation et la validation serait un excellent outil pour prévenir les régressions de sécurité du service.

La validation est assez simple à tester. Au lieu d'utiliser les méthodes de l'hôte de test que j'ai déjà décrites, vous devriez utiliser leurs variantes TryXx.

1. [TestMethod]

2. [Description("Tests that the UpdateBook operation returns validation errors when passed an invalid book")]

3. public void UpdateBook_SetsValidationErrors()

4. {

5. Book original = this._bookRepository.GetEntities().First();

6. Book book = new Book

7. {

8. AddedDate = original.AddedDate,

9. ASIN = "Invalid!",

10. Author = original.Author,

11. BookID = original.BookID,

12. Category = original.Category,

13. CategoryID = original.CategoryID,

14. Description = original.Description,

15. PublishDate = original.PublishDate,

16. Title = original.Title,

17. };

19. IList<ValidationResult> validationErrors;

20. bool success = this._domainServiceTestHost.TryUpdate(

21. book, original, out validationErrors);

23. Assert.IsFalse(success,

24. "Operation should have validation errors");

25. Assert.AreEqual(1, validationErrors.Count,

26. "Operation should return validation errors");

27. Assert.IsTrue(validationErrors[0].MemberNames.Single() == "ASIN",

28. "Operation should return a validation error for 'ASIN'");

29. }

Ce test fait appel à UpdateBook avec des données non valides, puis vérifie si les erreurs de validation qui en résultent sont celles que nous attendons.

Les tests d'autorisation suivent une approche différente. Au lieu d'utiliser une méthode d'hôte de test différente, ils définissent une entité personnalisée qui sera utilisée lors de l'appel de l'opération DomainService. Tandis que l'hôte de test prend par défaut un utilisateur anonyme, un constructeur alternatif vous permet de spécifier l'utilisateur, ce qui est intéressant pour votre cas de test.

1. this._domainServiceTestHost =

2. new DomainServiceTestHost<BookClubDomainService>(

3. this.CreateDomainService,

4. BookClubDomainServiceTest.authenticatedUser);

Conclusion

Pour tout résumer, la DomainServiceTestHost en combinaison avec une IDomainServiceFactory et le pattern Repository simplifient le test unitaire de vos DomainService de façon isolée et fiable. De même, l'hôte de test n'étant qu'un type .NET, il devrait être compatible avec tous les outils de test et frameworks avec lesquels vous souhaitez l'utiliser. Espérons que cette série vous a donné un bon aperçu des tests unitaires d'un DomainService.

Une note sur la DomainServiceTestHost

Au moment de la version initiale de cet hôte de test, il peut toujours y avoir quelques cas limites pour des jeux de modification d'association et de composition qui ne sont pas pris en charge. Si vous en voyez, veuillez me faire savoir afin que je puisse mettre en place un scénario pour une version ultérieure.

Liens

- Les sources de l'exemple sur les tests unitaires

- NuGet: RIAServices.UnitTesting

Remerciements

Je tiens ici à remercier Kyle McClellan de m'avoir autorisé à traduire son article.
Je remercie tomlev pour sa relecture technique et ses propositions.
Je remercie également _Max_ pour sa relecture orthographique et ses propositions.