Per visualizzare l'articolo in inglese, selezionare la casella di controllo Inglese. È possibile anche visualizzare il testo inglese in una finestra popup posizionando il puntatore del mouse sopra il testo.
Traduzione
Inglese

Procedura dettagliata: utilizzo dello sviluppo basato su test con ASP.NET MVC

In questa procedura dettagliata viene illustrato come sviluppare un'applicazione ASP.NET MVC in Visual Studio utilizzando l'approccio di sviluppo basato su test. MVC è stato progettato per consentire la testabilità senza la necessità di dipendenze da un server Web (IIS), da un database o da classi esterne, contrariamente a quanto avviene con gli unit test per le pagine Web Form che richiedono un server Web.

In questa procedura dettagliata verranno creati test per un controller MVC prima di implementare la funzionalità del controller. L'attenzione maggiore è rivolta alla concezione dello scopo del controller scrivendo gli unit test prima di implementare il controller stesso, aspetto importante della filosofia dello sviluppo basato su test. Per una panoramica della filosofia dello sviluppo basato su test di MVC, vedere l'intervento It’s Not TDD, It’s Design By Example nel blog di Brad Wilson.

Nella procedura dettagliata verranno creati un progetto e gli unit test per un'applicazione che è possibile utilizzare per visualizzare e modificare i contatti. Un contatto dispone di un nome, un cognome, un numero telefonico e un alias di posta elettronica.

Come complemento a questo argomento è disponibile un progetto di Visual Studio con codice sorgente che è possibile scaricare. Il download contiene i progetti MVC completati Csharp e VB, nelle cartelle cs e vb, e i progetti Csharp e VB nella cartella QuickStart. Nella prima parte di questa procedura dettagliata viene illustrato come creare un progetto MVC in Visual Studio, come aggiungere un modello di dati e come aggiungere metadati al modello di dati. Se si ha familiarità con le procedure per la creazione di un progetto ASP.NET MVC e di un modello di dati Entity Framework, è possibile utilizzare i progetti presenti nel progetto scaricabile all'interno della cartella QuickStart e andare alla sezione Adding a Repository più avanti in questa procedura dettagliata.

Per completare questa procedura dettagliata, è necessario:

  • Microsoft Visual Studio 2008 Service Pack 1 o versione successiva.

    NotaNota:

    Visual Studio Standard Edition e Visual Web Developer Express Edition non supportano la creazione di progetti di unit test.

  • Framework di ASP.NET MVC 2. Per scaricare la versione più aggiornata del framework, vedere la pagina di download di ASP.NET MVC.

  • File di database Contact.mdf. Questo file di database fa parte del progetto di esempio che è possibile scaricare per tale progetto: Download.

In questa sezione viene creata una nuova soluzione di Visual Studio che include sia il progetto di applicazione che un progetto di test.

NotaNota:

In questa procedura dettagliata viene utilizzato il framework unit test di Visual Studio. Per informazioni sull'aggiunta di un altro framework unit test, vedere Walkthrough: Creating a Basic MVC Project with Unit Tests in Visual Studio.

Per creare un'applicazione MVC con unit test

  1. In Visual Studio scegliere Nuovo progetto dal menu File.

  2. Nella finestra di dialogo Nuovo progetto in Modelli installati aprire il nodo Visual C# o Visual Basic, quindi selezionare Web.

  3. Selezionare il modello Applicazione Web ASP.NET MVC.

  4. Denominare la soluzione MvcContacts.

  5. Fare clic su OK.

  6. Quando viene visualizzata la finestra di dialogo Creazione progetto unit test, verificare che sia selezionato Sì, crea un progetto di unit test, quindi fare clic su OK.

    In Visual Studio viene creata una soluzione che contiene due progetti, uno denominato MvcContacts e l'altro denominato MvcContacts.Tests.

  7. Scegliere Esegui dal menu Test, quindi fare clic su Tutti i test nella soluzione.

    I risultati verranno visualizzati nella finestra Risultati test. I test vengono superati.

  8. Nel progetto MvcContacts.Tests aprire ed esaminare la classe di test del controller di account (MvcContacts\MvcContacts.Tests\Controllers\AccountControllerTest) e la classe di modello del controller di account (MvcContacts\Models\AccountModels).

    Queste classi forniscono un'introduzione alla creazione di interfacce fittizie e allo sviluppo basato su test. Il mocking è il processo di creazione di semplici oggetti sostitutivi (fittizi) per le dipendenze in una classe in modo tale da poter testare la classe senza le dipendenze. Per testare le interfacce, viene in genere creata una classe fittizia che implementa l'interfaccia che si desidera testare. Ad esempio, la classe MockMembershipService nella classe di test del controller di account implementa l'interfaccia IMembershipService per sostituire con oggetti fittizi i membri che fanno parte delle classi di appartenenza come i metodi ValidateUser, CreateUser e ChangePassword. La classe MockMembershipService consente di testare i metodi di azione che creano gli account utente, convalidano le informazioni di registrazione dell'utente e modificano la password di un utente senza dovere creare un'istanza di una classe di appartenenza come Membership.

In questa procedura dettagliata viene utilizzato un modello EDM (Entity Data Model) creato dal database Contact incluso nel progetto di esempio scaricabile. È necessario scaricare il progetto per ottenere il file Contact.mdf. Per ulteriori informazioni, vedere la sezione Prerequisiti descritta in precedenza in questa procedura dettagliata.

Per creare un modello di database

  1. In Esplora soluzioni fare clic con il pulsante destro del mouse sulla cartella App_Data del progetto MvcContacts, scegliere Aggiungi, quindi fare clic su Elemento esistente.

    Verrà visualizzata la finestra di dialogo Aggiungi elemento esistente.

  2. Passare alla cartella che contiene il file Contact.mdf, selezionarlo, quindi fare clic su Aggiungi.

  3. In Esplora soluzioni fare clic con il pulsante destro del mouse sul progetto MvcContacts, scegliere Aggiungi, quindi fare clic su Nuovo elemento.

    Verrà visualizzata la finestra di dialogo Aggiungi nuovo elemento.

  4. In Modelli installati aprire il nodo Visual C#, selezionare Dati, quindi selezionare il modello ADO.NET Entity Data Model.

  5. Nella casella Nome immettere ContactModel, quindi fare clic su Aggiungi.

    Verrà visualizzata la finestra Procedura guidata Entity Data Model.

  6. In Contenuto del modello selezionare Genera da database, quindi fare clic su Avanti.

  7. In Specificare la connessione dati che deve essere utilizzata dall'applicazione per connettersi al database selezionare Contact.mdf.

  8. Verificare che la casella di controllo Salva impostazioni stringa di connessione entity in Web.Config come sia selezionata. È possibile lasciare il nome della stringa di connessione predefinito.

  9. Fare clic su Avanti.

    Nella procedura guidata viene visualizzata una pagina in cui è possibile specificare quali oggetti di database includere nel proprio modello.

  10. Selezionare il nodo Tabelle per scegliere la tabella Contacts. È possibile lasciare lo spazio dei nomi del modello predefinito.

  11. Fare clic su Fine.

    Verrà visualizzato ADO.NET Entity Data Model Designer. Chiudere la finestra di progettazione.

In questa sezione verranno aggiunti i metadati del contatto. I metadati della classe del contatto non verranno utilizzati negli unit test, ma servono per rendere più completo l'esempio poiché forniscono la convalida automatizzata dei dati lato client e lato server.

Per aggiungere i metadati del modello

  1. Nella cartella MvcContacts\Models creare un nuovo file di classe denominato ContactMD.

    In tale file viene aggiunta la classe ContactMD contenente i metadati dell'oggetto entità Contact che fa parte del modello di dati utilizzato in questa procedura dettagliata.

  2. Sostituire il codice del file con il codice seguente.

    
    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; }
            }
        }
    } 
    
    
    

In MVC è consigliabile non includere nel controller il modello Entity Data Model (EDM) né altro codice del framework di accesso ai dati. Utilizzare invece il modello di repository. Il repository si trova tra l'applicazione e l'archivio dati. Un repository separa la logica di business dalle interazioni con il database sottostante e concentra l'accesso ai dati in un'unica area, semplificandone la creazione la gestione.

Il repository restituisce gli oggetti dal modello di dominio. Per i modelli semplici, ad esempio il modello utilizzato in questa procedura dettagliata, gli oggetti restituiti dal modello EDM, LINQ to SQL e da altri modelli di dati vengono qualificati come oggetti del dominio.

Per le applicazioni più complesse, potrebbe essere necessario un livello di mapping. Un livello di mapping non risulta necessariamente inefficiente. I provider LINQ possono creare query efficienti all'archivio dati di back-end, ovvero possono eseguire una query utilizzando un numero minimo di oggetti intermedi.

Il repository non richiede la conoscenza del modello EDM, LINQ to SQL o di qualsiasi altro modello di dati utilizzato. Anche se LINQ non viene analizzato in questa procedura dettagliata, l'utilizzo di LINQ come astrazione delle query significa che è possibile nascondere il meccanismo di archiviazione dei dati. In questo modo è ad esempio possibile utilizzare SQL Server per la produzione e LINQ to Objects sulle raccolte in memoria per il test.

Il test dei metodi di azione in un controller che accedono direttamente al modello EDM richiede una connessione a un database poiché i metodi di azione sono dipendenti dal modello EDM, il quale è dipendente da un database. Nel codice seguente viene illustrato un controller MVC che utilizza direttamente l'entità Contact del modello EDM. Tale codice rappresenta un esempio molto semplice del motivo per cui l'utilizzo di chiamate diverse del database nei metodi di azione rende difficile il test del metodo di azione. Ad esempio, gli unit test che modificano ed eliminano i dati cambiano lo stato del database, rendendo pertanto necessaria la pulizia del database a ogni passaggio degli unit test. Inoltre, le chiamate del database sono molto costose, mentre gli unit test devono essere leggeri in modo tale da poter essere eseguiti frequentemente durante lo sviluppo dell'applicazione.


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");
    }
} 


Il modello di repository presenta i vantaggi seguenti:

  • Fornisce un punto di sostituzione per gli unit test. È possibile testare facilmente la logica di business senza un database e le altre dipendenze esterne.

  • È possibile rimuovere le query duplicate e i modelli di accesso ai dati ed effettuarne il refactoring nel repository.

  • Nei metodi del controller è possibile utilizzare parametri fortemente tipizzati consentendo al compilatore di rilevare gli errori di digitazione dei dati ogni volta che viene eseguita la compilazione anziché in fase di esecuzione durante il test.

  • L'accesso ai dati è centralizzato offrendo i vantaggi seguenti:

    • Maggiore separazione delle problematiche, un altro caposaldo di MVC che aumenta la manutenibilità e la leggibilità.

    • Implementazione semplificata della memorizzazione centralizzata dei dati nella cache.

    • Un'architettura più flessibile e meno integrata che può adattarsi man mano che si evolve la progettazione complessiva dell'applicazione.

  • Il comportamento può essere associato ai dati correlati. È possibile ad esempio calcolare i campi o applicare relazioni o regole business complesse tra gli elementi dati all'interno di un'entità.

  • È possibile applicare un modello di dominio in modo da semplificare una logica di business complessa.

L'utilizzo del modello di repository con MVC e con lo sviluppo basato su test richiede in genere la creazione di un'interfaccia per la classe di accesso ai dati. L'interfaccia del repository semplificherà l'inserimento di un repository fittizio quando si esegue lo unit test dei metodi del controller.

In questa sezione verrà aggiunto un repository di contatti, ovvero una classe utilizzata per salvare i contatti in un database. Verrà inoltre aggiunta un'interfaccia per il repository di contatti.

Per aggiungere un repository

  1. Nella cartella MvcContacts\Models creare un nuovo file di classe e aggiungere una classe denominata IContactRepository.

    La classe IContactRepository conterrà l'interfaccia per l'oggetto del repository.

  2. Sostituire il codice nel file di classe con il codice seguente:

    
    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. Nella cartella MvcContacts\Models creare una nuova classe denominata EntityContactManagerRepository.

    La classe EntityContactManagerRepository implementerà l'interfaccia IContactRepository per l'oggetto del repository.

  4. Sostituire il codice nella classe EntityContactManagerRepository con il codice seguente:

    
    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();
            }
    
        }
    } 
    
    
    

In questa sezione verrà aggiunta un'implementazione fittizia del repository, verranno aggiunti gli unit test e verrà implementata la funzionalità dell'applicazione dagli unit test.

Per implementare il repository fittizio

  1. Nel progetto MvcContacts.Tests creare una cartella Models.

  2. Nella cartella MvcContacts.Tests\Models creare una nuova classe denominata MocContactRepository.

    La classe MocContactRepository implementerà l'interfaccia IContactRepository creata in precedenza e conterrà un repository semplice per la gestione della progettazione dell'applicazione.

  3. Sostituire il codice nella classe MocContactRepository con il codice seguente:

    
    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));
            }
    
        }
    } 
    
    
    

Per aggiungere il supporto di test

  1. Nel progetto MvcContacts aprire il file Controllers\HomeController.cs. Sostituire il codice nel file Controllers\HomeController.cs con il codice seguente:

    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();
            }
        }
    }
    

    Questa classe contiene due costruttori: uno è un costruttore senza parametri, l'altro accetta un parametro di tipo IContactRepository. Questo costruttore verrà utilizzato dagli unit test per passare il repository fittizio. Il costruttore senza parametri crea un'istanza della classe EF_ContactRepository e viene chiamato dalla pipeline MVC quando viene richiamato un metodo di azione nel controller.

  2. Chiudere il file HomeController.

  3. Nel progetto MvcContacts.Test aprire il file Controllers\HomeControllerTest e sostituire il codice nel file con il codice seguente:

    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;
                    }
                }
            }
        }
    }
    

    Il codice nell'esempio precedente contiene due overload del metodo che ottengono un contatto (GetContact) e un metodo per ottenere l'oggetto HomeController. Gli unit test chiameranno i metodi nell'oggetto HomeController. Il codice contiene inoltre una classe per sostituire con oggetti fittizi l'oggetto HttpContext. La classe MockHttpContext utilizzata in questo esempio è una versione semplificata di quella presente nel file di classe AcountControllerTest creato durante la creazione di un nuovo progetto MVC con gli unit test.

Uno dei concetti principali dello sviluppo basato su test con MVC è che ogni test deve basarsi su un requisito specifico in un metodo di azione. Il test non deve verificare il database o gli altri componenti, anche se questi componenti devono essere testati negli unit test di accesso ai dati e nei test di integrazione. Un altro obiettivo è che i nomi dei test devono essere molto descrittivi. L'utilizzo di nomi generali brevi come Creat_Post_Test1 renderà più difficile l'individuazione del test quando si dispone di centinaia di test.

In questa parte della procedura dettagliata si presupporrà che nella progettazione venga chiamato il metodo predefinito del controller Home per restituire un elenco di contatti. Il metodo predefinito del controller è Index, pertanto il primo test verificherà che il controller restituisca la visualizzazione dell'indice. In un passaggio precedente di questa procedura dettagliata sono state apportate alcune modifiche al metodo Index in modo da restituire un oggetto ViewResult anziché l'oggetto ActionResult più generale. Quando si è certi che un metodo restituisce sempre un oggetto ViewResult, è possibile semplificare gli unit test restituendo un oggetto ViewResult dal metodo del controller. Quando si restituisce un oggetto ViewResult, lo unit test non deve eseguire il cast dell'oggetto ActionResult tipico a un oggetto ViewResult come parte del test ottenendo in tal modo test più semplice e più leggibili.

Per aggiungere il primo test

  1. Nella classe HomeControllerTest aggiungere uno unit test denominato Index_Get_AsksForIndexView per verificare che il metodo Index restituisca un visualizzazione denominata Index.

    Nell'esempio seguente viene illustrato lo unit test completato.

    
    [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. Scegliere Esegui dal menu Test, quindi fare clic su Tutti i test nella soluzione.

    I risultati verranno visualizzati nella finestra Risultati test. Come previsto, lo unit test Index_Get_AsksForIndexView non viene superato.

  3. Implementare il metodo Index seguente della classe HomeController in modo da restituire un elenco di tutti i contatti.

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

    In base alla filosofia dello sviluppo basato su test, scrivere solo il codice sufficiente per soddisfare il test di progettazione.

  4. Eseguire i test. Questa volta lo unit test Index_Get_AsksForIndexView viene superato.

In questa sezione verrà verificato di poter recuperare tutti i contatti. Non è necessario creare unit test che testano l'accesso ai dati. È importante verificare che l'applicazione sia in grado di accedere al database e di recuperare i contatti, ma a tale scopo esiste un test di integrazione e non uno unit test di sviluppo basato su test.

Per aggiungere un test per il recupero di contatti

  • Creare un test che aggiunge due contatti fittizi al repository fittizio nella classe HomeControllerTest e che quindi verifica che siano contenuti nell'oggetto ViewDataModel all'interno della visualizzazione Index.

    Nell'esempio seguente viene illustrato il test completo.

    
    [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);
    } 
    
    
    

A questo punto verrà eseguito il test del processo di creazione di un nuovo contatto. Il primo test verifica che un'operazione HTTP di tipo POST sia stata completata correttamente e che sia stato richiamato il metodo Create utilizzando dati che contengono deliberatamente errori del modello. Il risultato non aggiungerà un nuovo contatto ma restituirà invece la visualizzazione HTTP Create di tipo GET che contiene i campi immessi e gli errori del modello. L'esecuzione di uno unit test in un controller non esegue la pipeline MVC o il processo di associazione del modello. L'errore del modello non viene pertanto rilevato durante il processo di associazione. Per risolvere tale problema, il test aggiunge un errore fittizio.

Per aggiungere un test per la creazione di un contatto

  1. Aggiungere il test seguente al progetto:

    
    [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);
    } 
    
    
    

    Nel codice viene illustrato come un tentativo di aggiungere un contatto contenente errori del modello restituisca la visualizzazione HTTP Create di tipo GET.

  2. Aggiungere il test seguente:

    
    [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));
    } 
    
    
    

    Nel codice viene illustrato come verificare che una chiamata HTTP POST al metodo Create consenta di aggiungere un contatto valido al repository.

Un test che viene spesso sottovalutato è quello di verificare la corretta gestione delle eccezioni da parte dei metodi. La classe MocContactRepository consente di impostare un'eccezione fittizia, simulando un'eccezione generata da un database quando viene violato un vincolo o si verifica un altro tipo di violazione. Poiché non possibile rilevare molte eccezioni del database con la convalida del modello, è importante verificare che il codice di gestione delle eccezioni funzioni correttamente. L'esempio seguente illustra come effettuare questa operazione.


[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'esempio scaricabile include più test di quelli illustrati in questo argomento. Per ulteriori informazioni sull'utilizzo di oggetti fittizi e sull'utilizzo della metodologia di sviluppo basato su test con i progetti MVC, consultare i test aggiuntivi e scrivere test per i metodi Delete e Edit.

Aggiunte alla community

AGGIUNGI
Mostra: