Pour afficher l’article en anglais, activez la case d’option Anglais. Vous pouvez aussi afficher la version anglaise dans une fenêtre contextuelle en faisant glisser le pointeur de la souris sur le texte.
Traduction
Anglais

Procédure pas à pas : utilisation du développement TDD avec ASP.NET MVC

Cette procédure pas à pas vous indique comment développer une application ASP.NET MVC dans Visual Studio à l'aide de l'approche de développement piloté par les tests (TDD). MVC a été conçu pour permettre le testabilité sans dépendances nécessaires à un serveur Web (IIS), une base de données ou des classes externes. (Ce comportement est contraire à celui des tests unitaires pour les pages Web Forms, qui requièrent un serveur Web.)

Dans cette procédure pas à pas, vous allez créer des tests pour un contrôleur MVC avant d'implémenter les fonctionnalités du contrôleur. L'accent est mis sur la conception de la finalité du contrôleur en écrivant des tests unitaires avant d'implémenter le contrôleur-même, ce qui constitue un aspect important de la philosophie TDD. (Pour une bonne vue d'ensemble de la philosophie de MVC TDD, consultez l'article It’s Not TDD, It’s Design By Example du blog de Brad Wilson.)

Dans la procédure pas à pas, vous allez créer un projet et des tests unitaires pour une application que vous pouvez utiliser pour afficher et modifier des contacts. Un contact regroupe un prénom, un nom, un numéro de téléphone et un alias de messagerie électronique.

Un projet Visual Studio (avec code source) est disponible pour accompagner cette rubrique : Download. Le téléchargement contient les projets MVC Csharp et VB terminés (dans les dossiers cs et vb) et les projets Csharp et VB dans le dossier QuickStart. La première partie de cette procédure pas à pas indique comment créer un projet MVC dans Visual Studio, ajouter un modèle de données et ajouter des métadonnées au modèle de données. Si vous connaissez les procédures de création d'un projet ASP.NET MVC et d'un modèle de données Entity Framework, vous pouvez alors utiliser les projets du projet téléchargeable sous le dossier QuickStart et passez à la section Adding a Repository de cette procédure.

Pour exécuter cette procédure pas à pas, vous avez besoin des éléments suivants :

  • Microsoft Visual Studio 2008 Service Pack 1 ou version ultérieure.

    RemarqueRemarque :

    Visual Studio Standard et Visual Web Developer Express Edition ne prennent pas en charge les projets de test unitaire.

  • Infrastructure ASP.NET MVC 2. Pour télécharger la version la plus récente de l'infrastructure, consultez la page de téléchargement d'ASP.NET MVC.

  • Fichier de base de données Contact.mdf. Ce fichier de base de données fait également partie de l'exemple de projet que vous pouvez télécharger pour ce projet :Télécharger.

De cette section, vous allez créer une solution Visual Studio qui comprend à la fois le projet d'application et un projet de test.

RemarqueRemarque :

Cette procédure pas à pas utilise l'infrastructure de test unitaire Visual Studio. Pour plus d'informations sur l'ajout d'une autre infrastructure de test unitaire, consultez Walkthrough: Creating a Basic MVC Project with Unit Tests in Visual Studio

Pour créer une application MVC avec tests unitaires

  1. Dans le menu Fichier de Visual Studio, cliquez sur Nouveau projet.

  2. Dans la boîte de dialogue Nouveau projet, sous Modèles installés, ouvrez le nœud Visual C# ou Visual Basic, puis sélectionnez Web.

  3. Sélectionnez le modèle Application Web ASP.NET MVC.

  4. Nommez la solution MvcContacts.

  5. Cliquez sur OK.

  6. Lorsque la boîte de dialogue Créer un projet de test unitaire s'affiche, vérifiez que l'option Oui, créer un projet de test unitaire est sélectionnée, puis cliquez sur OK.

    Visual Studio crée une solution qui contient deux projets, l'un nommé MvcContacts et l'autre nommé MvcContacts.Tests.

  7. Dans le menu Test, cliquez sur Exécuter, puis sur Tous les tests de la solution.

    Les résultats s'affichent dans la fenêtre Résultats des tests. Les tests passent.

  8. Dans le projet MvcContacts.Tests, ouvrez et examinez la classe du test du contrôleur de compte (MvcContacts\MvcContacts.Tests\Controllers\AccountControllerTest) et la classe du modèle du contrôleur de compte (MvcContacts\Models\AccountModels).

    Ces classes fournissent une bonne introduction à la création des interfaces fictives et au développement TDD. La création d'objets fictifs consiste à créer des objets de substitution (fictifs) simples pour les dépendances dans une classe, afin de pouvoir tester la classe sans les dépendances. Pour tester des interfaces, vous créez en général une classe fictive qui implémente l'interface vous souhaitez tester. Par exemple, la classe MockMembershipService dans la classe du test du contrôleur de compte implémente l'interface IMembershipService pour créer des membres fictifs qui font partie de classes d'appartenance, telles que les méthodes ValidateUser, CreateUser et ChangePassword. La classe MockMembershipService vous permet de tester les méthodes d'action qui créent des comptes d'utilisateurs, valider les informations d'enregistrement des utilisateurs et modifier un mot de passe utilisateur sans devoir instancier une classe d'appartenance comme Membership.

Cette procédure pas à pas utilise un modèle EDM (Entity Data Model) créé à partir de la base de données Contact incluse dans l'exemple de projet téléchargeable. (Vous devez télécharger le projet pour obtenir le fichier Contact.mdf. Pour plus d'informations, consultez la section Conditions préalables requises plus haut dans cette procédure.)

Pour créer le modèle de base de données

  1. Dans l'Explorateur de solutions, cliquez avec le bouton droit sur le dossier App_Data du projet MvcContacts, cliquez sur Ajouter, puis sur Élément existant.

    La boîte de dialogue Ajouter un élément existant s'affiche.

  2. Naviguez jusqu'au dossier qui contient le fichier Contact.mdf, sélectionnez le fichier Contact.mdf, puis cliquez sur Ajouter.

  3. Dans l'Explorateur de solutions, cliquez avec le bouton droit sur le projet MvcContacts, cliquez sur Ajouter, puis sur Nouvel élément.

    La boîte de dialogue Ajouter un nouvel élément s'affiche.

  4. Sous Modèles installés, ouvrez le nœud Visual C#, sélectionnez Données, puis le modèle ADO.NET Entity Data Model.

  5. Dans la zone Nom, entrez ContactModel, puis cliquez sur Ajouter.

    La fenêtre Assistant EDM s'affiche.

  6. Sous Qu'est-ce que le modèle doit contenir, sélectionnez Générer à partir de la base de données, puis cliquez sur Suivant.

  7. Sous Quelle connexion de données votre application doit-elle utiliser pour établir une connexion à la base de données ?, sélectionnez Contact.mdf.

  8. Assurez-vous que la case à cocher Enregistrer les paramètres de connexion de l'entité dans Web.config en tant que est activée. Vous pouvez laisser le nom de chaîne de connexion par défaut.

  9. Cliquez sur Suivant.

    L'Assistant affiche une page vous permettant de spécifier les objets de base de données à inclure dans votre modèle.

  10. Sélectionnez le nœud Tables pour sélectionner la table Contacts. Vous pouvez laisser l'espace de noms du modèle par défaut.

  11. Cliquez sur Terminer.

    ADO.NET Entity Data Model Designer s'affiche. Fermez le concepteur.

De cette section, vous allez ajouter des métadonnées de contact. Les métadonnées de classe de contact ne sont pas utilisées dans les tests unitaires. Toutefois, elles permettent de compléter l'exemple car elles fournissent la validation automatique des données côté client et côté serveur.

Pour ajouter des métadonnées de modèle

  1. Dans le dossier MvcContacts\Models, créez un fichier de classe nommé ContactMD.

    Dans ce fichier, vous allez ajouter une classe (ContactMD) qui contient les métadonnées pour l'objet d'entité Contact qui fait partie du modèle de données utilisé dans cette procédure pas à pas.

  2. Remplacez le code du fichier par le code suivant :

    
    using System.ComponentModel.DataAnnotations;
    
    namespace MvcContacts.Models {
        [MetadataType(typeof(ContactMD))]
        public partial class Contact {
            public class ContactMD {
                [ScaffoldColumn(false)]
                public object Id { get; set; }
                [Required()]
                public object FirstName { get; set; }
                [Required()]
                public object LastName { get; set; }
                [RegularExpression(@"^\d{3}-?\d{3}-?\d{4}$")]
                public object Phone { get; set; }
                [Required()]
                [DataType(DataType.EmailAddress)]
                [RegularExpression(@"^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$")]
                public object Email { get; set; }
            }
        }
    } 
    
    
    

Une recommandation dans MVC est de ne pas inclure le modèle EDM (Entity Data Model) ou tout autre code d'infrastructure d'accès aux données dans votre contrôleur. Utilisez plutôt le modèle de référentiel. Le référentiel se trouve entre l'application et le magasin de données. Un référentiel sépare la logique métier des interactions avec la base de données sous-jacente et concentre votre accès aux données dans une zone, qui est ainsi plus simple à créer et à maintenir.

Le référentiel retourne des objets du modèle de domaine. Pour les modèles simples, tels que le modèle utilisé dans cette procédure pas à pas, les objets retournés par les modèles EDM, LINQ to SQL et autres modèles de données sont considérés comme objets de domaine.

Pour des applications plus complexes, une couche de mappage peut être nécessaire. Une couche de mappage n'est pas nécessairement inefficace. Les fournisseurs LINQ peuvent créer des requêtes efficaces pour le magasin de données principal (autrement dit, ils peuvent lancer une requête à l'aide d'un nombre minime d'objets intermédiaires).

Le référentiel ne nécessite pas de connaissances spécifiques relatives aux modèles EDM, LINQ to SQL ou tout autre modèle de données que vous utilisez. (Bien que LINQ ne soit pas couvert dans cette procédure pas à pas, l'utilisation de LINQ comme moyen d'abstraction de requête permet de masquer le mécanisme de stockage des données. Par exemple, cela vous permet d'utiliser SQL Server pour la production et LINQ to Objects sur des collections en mémoire pour le test.)

Le test des méthodes d'action dans un contrôleur qui accèdent directement au modèle EDM requiert une connexion à une base de données, parce que les méthodes d'action sont dépendantes du modèle EDM (lequel est dépendant d'une base de données). Le code suivant affiche un contrôleur MVC qui utilise directement l'entité Contact du modèle EDM. Il fournit un exemple simple de la raison pour laquelle le mélange d'appels de base de données dans les méthodes d'action rend la méthode d'action difficile à tester. Par exemple, les tests unitaires qui modifient et suppriment les données changent l'état de la base de données. Cela requiert que chaque exécution des tests unitaires ait lieu sur une nouvelle installation de base de données. En outre, les appels de base de données sont très coûteux, alors que les tests unitaires sont légers et peuvent donc être fréquemment exécutés au cours du développement de l'application.


public class NotTDDController : Controller {

    ContactEntities _db = new ContactEntities();

    public ActionResult Index() {
        var dn = _db.Contacts;
        return View(dn);
    }

    public ActionResult Edit(int id) {
        Contact prd = _db.Contacts.FirstOrDefault(d => d.Id == id);
        return View(prd);
    }

    [HttpPost]
    public ActionResult Edit(int id, FormCollection collection) {
        Contact prd = _db.Contacts.FirstOrDefault(d => d.Id == id);
        UpdateModel(prd);
        _db.SaveChanges();
        return RedirectToAction("Index");
    }
} 



public class NotTDDController : Controller {

    ContactEntities _db = new ContactEntities();

    public ActionResult Index() {
        var dn = _db.Contacts;
        return View(dn);
    }

    public ActionResult Edit(int id) {
        Contact prd = _db.Contacts.FirstOrDefault(d => d.Id == id);
        return View(prd);
    }

    [HttpPost]
    public ActionResult Edit(int id, FormCollection collection) {
        Contact prd = _db.Contacts.FirstOrDefault(d => d.Id == id);
        UpdateModel(prd);
        _db.SaveChanges();
        return RedirectToAction("Index");
    }
} 


Le modèle de référentiel offre les avantages suivants :

  • Il fournit un point de substitution pour les tests unitaires. Vous pouvez tester facilement la logique métier sans base de données et autres dépendances externes.

  • Les requêtes dupliquées et modèles d'accès aux données peuvent être supprimés et refactorisés dans le référentiel.

  • Les méthodes de contrôleur peuvent utiliser des paramètres fortement typés, ce qui signifie que le compilateur rencontre des erreurs de frappe dans les données à chaque compilation au lieu de rechercher les erreurs de frappe au moment de l'exécution dans le cadre du test.

  • L'accès aux données est centralisé, ce qui offre les avantages suivants :

    • Plus grande séparation des problèmes (SoC), autre pilier de MVC, qui augmente la maintenabilité et la lisibilité.

    • Implémentation simplifiée de la mise en cache de données centralisée.

    • Une architecture plus flexible et avec moins d'associations qui peut être adaptée en fonction de l'évolution de la conception globale de l'application.

  • Le comportement peut être associé à des données associées. Par exemple, vous pouvez calculer des champs ou appliquer des relations complexes ou des règles métier entre les éléments de données dans une entité.

  • Un modèle de domaine peut être appliqué pour simplifier une logique métier complexe.

L'utilisation du modèle de référentiel avec MVC et le développement TDD requiert généralement que vous créiez une interface pour votre classe d'accès aux données. L'interface de référentiel facilite l'ajout d'un référentiel fictif lorsque vous effectuez un test unitaire sur nos méthodes de contrôleur.

De cette section, vous allez ajouter un référentiel de contact, qui est une classe utilisée pour enregistrer les contacts dans une base de données. Vous allez également ajouter une interface pour le référentiel de contact.

Pour ajouter un référentiel

  1. Dans le dossier MvcContacts\Models, créez un fichier de classe et ajoutez une classe nommée IContactRepository.

    La classe IContactRepository contient l'interface pour l'objet de référentiel.

  2. Remplacez le code contenu dans le fichier de classe par le code suivant :

    
    using System;
    using System.Collections.Generic;
    
    namespace MvcContacts.Models {
        public interface IContactRepository {
            void CreateNewContact(Contact contactToCreate);
            void DeleteContact(int id);
            Contact GetContactByID(int id);
            IEnumerable<Contact> GetAllContacts();
            int SaveChanges();
    
        }
    } 
    
    
    
  3. Dans le dossier MvcContacts\Models, créez une classe nommée EntityContactManagerRepository.

    La classe EntityContactManagerRepository implémente l'interface IContactRepository pour l'objet de référentiel.

  4. Remplacez le code dans la classe EntityContactManagerRepository par le code suivant :

    
    using System.Collections.Generic;
    using System.Linq;
    
    namespace MvcContacts.Models {
        public class EF_ContactRepository : MvcContacts.Models.IContactRepository {
    
            private ContactEntities _db = new ContactEntities();
    
            public Contact GetContactByID(int id) {
                return _db.Contacts.FirstOrDefault(d => d.Id == id);
            }
    
            public IEnumerable<Contact> GetAllContacts() {
                return _db.Contacts.ToList();
            }
    
            public void CreateNewContact(Contact contactToCreate) {
                _db.AddToContacts(contactToCreate);
                _db.SaveChanges();
             //   return contactToCreate;
            }
    
            public int SaveChanges() {
                return _db.SaveChanges();
            }
    
            public void DeleteContact(int id) {
                var conToDel = GetContactByID(id);
                _db.Contacts.DeleteObject(conToDel);
                _db.SaveChanges();
            }
    
        }
    } 
    
    
    

De cette section, vous allez ajouter une implémentation fictive du référentiel, ajouter des tests unitaires et implémenter les fonctionnalités de l'application à partir de tests unitaires.

Pour implémenter le référentiel fictif

  1. Dans le projet MvcContacts.Tests, créez un dossier Models.

  2. Dans le dossier MvcContacts.Tests\Models, créez une classe nommée MocContactRepository.

    La classe MocContactRepository implémente l'interface IContactRepository créée précédemment et dispose d'un référentiel simple pour diriger la conception des applications.

  3. Remplacez le code dans la classe MocContactRepository par le code suivant :

    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    using MvcContacts.Models;
    
    namespace MvcContacts.Tests.Models {
        class InMemoryContactRepository : MvcContacts.Models.IContactRepository {
            private List<Contact> _db = new List<Contact>();
    
            public Exception ExceptionToThrow { get; set; }
            //public List<Contact> Items { get; set; }
    
            public void SaveChanges(Contact contactToUpdate) {
    
                foreach (Contact contact in _db) {
                    if (contact.Id == contactToUpdate.Id) {
                        _db.Remove(contact);
                        _db.Add(contactToUpdate);
                        break;
                    }
                }
            }
    
            public void Add(Contact contactToAdd) {
                _db.Add(contactToAdd);
            }
    
            public Contact GetContactByID(int id) {
                return _db.FirstOrDefault(d => d.Id == id);
            }
    
            public void CreateNewContact(Contact contactToCreate) {
                if (ExceptionToThrow != null)
                    throw ExceptionToThrow;
    
                _db.Add(contactToCreate);
               // return contactToCreate;
            }
    
            public int SaveChanges() {
                return 1;
            }
    
            public IEnumerable<Contact> GetAllContacts() {
                return _db.ToList();
            }
    
    
            public void DeleteContact(int id) {
                _db.Remove(GetContactByID(id));
            }
    
        }
    } 
    
    
    
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    using MvcContacts.Models;
    
    namespace MvcContacts.Tests.Models {
        class InMemoryContactRepository : MvcContacts.Models.IContactRepository {
            private List<Contact> _db = new List<Contact>();
    
            public Exception ExceptionToThrow { get; set; }
            //public List<Contact> Items { get; set; }
    
            public void SaveChanges(Contact contactToUpdate) {
    
                foreach (Contact contact in _db) {
                    if (contact.Id == contactToUpdate.Id) {
                        _db.Remove(contact);
                        _db.Add(contactToUpdate);
                        break;
                    }
                }
            }
    
            public void Add(Contact contactToAdd) {
                _db.Add(contactToAdd);
            }
    
            public Contact GetContactByID(int id) {
                return _db.FirstOrDefault(d => d.Id == id);
            }
    
            public void CreateNewContact(Contact contactToCreate) {
                if (ExceptionToThrow != null)
                    throw ExceptionToThrow;
    
                _db.Add(contactToCreate);
               // return contactToCreate;
            }
    
            public int SaveChanges() {
                return 1;
            }
    
            public IEnumerable<Contact> GetAllContacts() {
                return _db.ToList();
            }
    
    
            public void DeleteContact(int id) {
                _db.Remove(GetContactByID(id));
            }
    
        }
    } 
    
    
    

Pour ajouter la prise en charge des tests

  1. Dans le projet MvcContacts, ouvrez le fichier Controllers\HomeController.cs. Remplacez le code du fichier Controllers\HomeController.cs par le code suivant :

    using System;
    using System.Web.Mvc;
    using MvcContacts.Models;
    
    namespace MvcContacts.Controllers {
        [HandleError]
        public class HomeController : Controller {
            IContactRepository _repository;
            public HomeController() : this(new EF_ContactRepository()) { }
            public HomeController(IContactRepository repository) {
                _repository = repository;
            }
            public ViewResult Index() {
                throw new NotImplementedException();
            }
        }
    }
    

    Cette classe contient deux constructeurs. Le premier est un constructeur sans paramètre. L'autre accepte un paramètre de type IContactRepository ; ce constructeur est utilisé par les tests unitaires qui le passent dans le référentiel fictif. Le constructeur sans paramètre crée une instance de la classe EF_ContactRepository et est appelé par le pipeline MVC lorsqu'une méthode d'action dans le contrôleur est appelée.

  2. Fermez le fichier HomeController.

  3. Dans le projet MvcContacts.Test, ouvrez le fichier Controllers\HomeControllerTest et remplacez le code existant par le code suivant :

    using System.Web.Mvc;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using MvcContacts.Controllers;
    
    using MvcContacts.Models;
    using MvcContacts.Tests.Models;
    using System.Web;
    using System.Web.Routing;
    using System.Security.Principal;
    
    namespace MvcContacts.Tests.Controllers {
        [TestClass]
        public class HomeControllerTest {
            
            Contact GetContact() {
                return GetContact(1, "Janet", "Gates");
            }
    
            Contact GetContact(int id, string fName, string lName) {
                return new Contact
                {
                    Id = id,
                    FirstName = fName,
                    LastName = lName,
                    Phone = "710-555-0173",
                    Email = "janet1@adventure-works.com"
                };
            }
    
            private static HomeController GetHomeController(IContactRepository repository) {
                HomeController controller = new HomeController(repository);
    
                controller.ControllerContext = new ControllerContext()
                {
                    Controller = controller,
                    RequestContext = new RequestContext(new MockHttpContext(), new RouteData())
                };
                return controller;
            }
    
    
            private class MockHttpContext : HttpContextBase {
                private readonly IPrincipal _user = new GenericPrincipal(
                         new GenericIdentity("someUser"), null /* roles */);
    
                public override IPrincipal User {
                    get {
                        return _user;
                    }
                    set {
                        base.User = value;
                    }
                }
            }
        }
    }
    

    Le code de l'exemple précédent contient deux surcharges de méthode qui obtiennent un contact (GetContact) et une méthode pour obtenir l'objet HomeController. Les tests unitaires appellent les méthodes dans l'objet HomeController. Le code contient également une classe pour créer un objet HttpContext fictif. La classe MockHttpContext utilisée dans cet exemple est une version simplifiée de celle qui se trouve dans le fichier de classe AcountControllerTest créé pour le nouveau projet MVC avec tests unitaires.

L'un des principes du développement TDD avec MVC est que chaque test doit piloter une spécification donnée dans une méthode d'action. Le test ne doit pas vérifier la base de données ou d'autres composants (bien que ces composants doivent être testés dans les tests unitaires d'accès aux données et dans le test d'intégration). Un autre principe est que les noms des tests doivent être très descriptifs ; des noms généraux courts tels que Creat_Post_Test1 ne permettent pas de comprendre facilement le but d'un test parmi des centaines de tests.

Dans cette partie de la procédure pas à pas, vous allez supposer que la conception appelle la méthode par défaut du contrôleur Home, pour recevoir une liste de contacts. La méthode par défaut pour le contrôleur est Index, le premier test vérifie donc que le contrôleur retourne la vue d'index. Lorsque vous avez apporté des modifications à la méthode Index plus tôt dans cette procédure pas à pas, vous l'avez modifié de sorte à ce qu'elle retourne un objet ViewResult, et non l'objet ActionResult plus général. Lorsque vous savez qu'une méthode retourne toujours un objet ViewResult, vous pouvez simplifier les tests unitaires en retournant un objet ViewResult de la méthode de contrôleur. Lorsque vous retournez un objet ViewResult, le test unitaire n'a pas besoin d'effectuer le cast de l'objet ActionResult type en un objet ViewResult dans le cadre du test, ce qui permet de disposer de tests plus simples et plus lisibles

Pour ajouter le premier test

  1. Dans la classe HomeControllerTest, ajoutez un test unitaire nommé Index_Get_AsksForIndexView qui vérifie que la méthode Index retourne une vue nommée Index.

    L'exemple suivant illustre le test unitaire terminé.

    
    [TestMethod]
    public void Index_Get_AsksForIndexView() {
        // Arrange
        var controller = GetHomeController(new InMemoryContactRepository());
        // Act
        ViewResult result = controller.Index();
        // Assert
        Assert.AreEqual("Index", result.ViewName);
    } 
    
    
    
    
    [TestMethod]
    public void Index_Get_AsksForIndexView() {
        // Arrange
        var controller = GetHomeController(new InMemoryContactRepository());
        // Act
        ViewResult result = controller.Index();
        // Assert
        Assert.AreEqual("Index", result.ViewName);
    } 
    
    
    
  2. Dans le menu Test, cliquez sur Exécuter, puis sur Tous les tests de la solution.

    Les résultats s'affichent dans la fenêtre Résultats des tests. Comme prévu, le test unitaire Index_Get_AsksForIndexView échoue.

  3. Implémentez la méthode Index suivante de la classe HomeController pour retourner une liste de tous les contacts.

    public ViewResult Index() {
                return View("Index", _repository.ListContacts());
            }
    

    Dans l'approche TDD, vous écrivez juste le code nécessaire pour satisfaire le test de conception.

  4. Exécutez les tests. Cette fois-ci, les tests unitaires Index_Get_AsksForIndexView réussissent.

De cette section, vous allez vérifier que vous pouvez récupérer tous les contacts. Vous ne souhaitez pas créer des tests unitaires qui testent l'accès aux données. Il est important de vérifier que votre application peut accéder à la base de données et récupérer les contacts, mais il s'agit d'un test d'intégration, pas d'un test unitaire TDD.

Pour ajouter un test de la récupération des contacts

  • Créez un test qui ajoute deux contacts fictifs au référentiel fictif dans la classe HomeControllerTest, puis vérifie qu'ils sont contenus dans l'objet ViewDataModel de la vue Index.

    L'exemple suivant illustre le test terminé.

    
    [TestMethod]
    public void Index_Get_RetrievesAllContactsFromRepository() {
        // Arrange
        Contact contact1 = GetContactNamed(1, "Orlando", "Gee");
        Contact contact2 = GetContactNamed(2, "Keith", "Harris");
        InMemoryContactRepository repository = new InMemoryContactRepository();
        repository.Add(contact1);
        repository.Add(contact2);
        var controller = GetHomeController(repository);
    
        // Act
        var result = controller.Index();
    
        // Assert
        var model = (IEnumerable<Contact>)result.ViewData.Model;
        CollectionAssert.Contains(model.ToList(), contact1);
        CollectionAssert.Contains(model.ToList(), contact1);
    } 
    
    
    
    
    [TestMethod]
    public void Index_Get_RetrievesAllContactsFromRepository() {
        // Arrange
        Contact contact1 = GetContactNamed(1, "Orlando", "Gee");
        Contact contact2 = GetContactNamed(2, "Keith", "Harris");
        InMemoryContactRepository repository = new InMemoryContactRepository();
        repository.Add(contact1);
        repository.Add(contact2);
        var controller = GetHomeController(repository);
    
        // Act
        var result = controller.Index();
    
        // Assert
        var model = (IEnumerable<Contact>)result.ViewData.Model;
        CollectionAssert.Contains(model.ToList(), contact1);
        CollectionAssert.Contains(model.ToList(), contact1);
    } 
    
    
    

Vous allez maintenant tester le processus de création d'un contact. Le premier test vérifie qu'une opération HTTP POST s'est terminée correctement et a appelée la méthode Create à l'aide de données qui contiennent délibérément des erreurs de modèle. Le résultat n'ajoute pas de nouveau contact mais, à la place, retourne la vue HTTP GET Create qui contient les champs vous a entrés et les erreurs de modèle. L'exécution d'un test unitaire sur un contrôleur n'exécute pas le pipeline MVC ou le processus de liaison du modèle. Par conséquent, l'erreur de modèle ne serait pas détectée pendant le processus de liaison. Pour remédier à cela, le test ajoute une erreur fictive.

Pour ajouter un test de création d'un contact

  1. Ajoutez le test suivant au projet :

    
    [TestMethod]
    public void Create_Post_ReturnsViewIfModelStateIsNotValid() {
        // Arrange
        HomeController controller = GetHomeController(new InMemoryContactRepository());
        // Simply executing a method during a unit test does just that - executes a method, and no more. 
        // The MVC pipeline doesn't run, so binding and validation don't run.
        controller.ModelState.AddModelError("", "mock error message");
        Contact model = GetContactNamed(1, "", "");
    
        // Act
        var result = (ViewResult)controller.Create(model);
    
        // Assert
        Assert.AreEqual("Create", result.ViewName);
    } 
    
    
    
    
    [TestMethod]
    public void Create_Post_ReturnsViewIfModelStateIsNotValid() {
        // Arrange
        HomeController controller = GetHomeController(new InMemoryContactRepository());
        // Simply executing a method during a unit test does just that - executes a method, and no more. 
        // The MVC pipeline doesn't run, so binding and validation don't run.
        controller.ModelState.AddModelError("", "mock error message");
        Contact model = GetContactNamed(1, "", "");
    
        // Act
        var result = (ViewResult)controller.Create(model);
    
        // Assert
        Assert.AreEqual("Create", result.ViewName);
    } 
    
    
    

    Le code montre comment une tentative d'ajout d'un contact qui contient des erreurs de modèle retourne la vue HTTP GET Create.

  2. Ajoutez le test suivant :

    
    [TestMethod]
    public void Create_Post_PutsValidContactIntoRepository() {
        // Arrange
        InMemoryContactRepository repository = new InMemoryContactRepository();
        HomeController controller = GetHomeController(repository);
        Contact contact = GetContactID_1();
    
        // Act
        controller.Create(contact);
    
        // Assert
        IEnumerable<Contact> contacts = repository.GetAllContacts();
        Assert.IsTrue(contacts.Contains(contact));
    } 
    
    
    
    
    [TestMethod]
    public void Create_Post_PutsValidContactIntoRepository() {
        // Arrange
        InMemoryContactRepository repository = new InMemoryContactRepository();
        HomeController controller = GetHomeController(repository);
        Contact contact = GetContactID_1();
    
        // Act
        controller.Create(contact);
    
        // Assert
        IEnumerable<Contact> contacts = repository.GetAllContacts();
        Assert.IsTrue(contacts.Contains(contact));
    } 
    
    
    

    Le code montre comment vérifier qu'une opération HTTP POST sur la méthode Create ajoute un contact valide au référentiel.

Un test qui est souvent oublié consiste à vérifier que les méthode gèrent correctement les exceptions. La classe MocContactRepository vous permet de définir une exception fictive, en simulant une exception qu'une base de données pourrait lever en cas de non-respect d'une contrainte ou toute autre violation. De nombreuses exceptions de base de données ne peuvent pas être détectées avec la validation du modèle, il est donc important de vérifier que le code de traitement des exceptions fonctionne correctement. L'exemple suivant montre comment effectuer cette opération.


[TestMethod]
public void Create_Post_ReturnsViewIfRepositoryThrowsException() {
    // Arrange
    InMemoryContactRepository repository = new InMemoryContactRepository();
    Exception exception = new Exception();
    repository.ExceptionToThrow = exception;
    HomeController controller = GetHomeController(repository);
    Contact model = GetContactID_1();

    // Act
    var result = (ViewResult)controller.Create(model);

    // Assert
    Assert.AreEqual("Create", result.ViewName);
    ModelState modelState = result.ViewData.ModelState[""];
    Assert.IsNotNull(modelState);
    Assert.IsTrue(modelState.Errors.Any());
    Assert.AreEqual(exception, modelState.Errors[0].Exception);
} 



[TestMethod]
public void Create_Post_ReturnsViewIfRepositoryThrowsException() {
    // Arrange
    InMemoryContactRepository repository = new InMemoryContactRepository();
    Exception exception = new Exception();
    repository.ExceptionToThrow = exception;
    HomeController controller = GetHomeController(repository);
    Contact model = GetContactID_1();

    // Act
    var result = (ViewResult)controller.Create(model);

    // Assert
    Assert.AreEqual("Create", result.ViewName);
    ModelState modelState = result.ViewData.ModelState[""];
    Assert.IsNotNull(modelState);
    Assert.IsTrue(modelState.Errors.Any());
    Assert.AreEqual(exception, modelState.Errors[0].Exception);
} 


L'exemple téléchargeable inclut d'autres tests, qui ne sont pas détaillés ici. Pour en savoir plus sur l'utilisation d'objets fictifs et sur l'utilisation de la méthodologie TDD avec des projets MVC, observez les tests supplémentaires et écrivez des tests pour les méthodes Delete et Edit.

Ajouts de la communauté

AJOUTER
Afficher: