Данная статья переведена с помощью средств машинного перевода. Чтобы просмотреть ее на английском языке, установите флажок Английский. Вы также можете просматривать английский текст во всплывающем окне, наводя указатель мыши на переведенный текст.
Перевод
Английский

Пошаговое руководство. Использование разработки на основе тестирования в ASP.NET MVC

Visual Studio 2010

В данном пошаговом руководстве описан процесс разработки приложения ASP.NET MVC в Visual Studio с помощью управляемой тестами разработки (TDD). Шаблон MVC обеспечивает возможности тестирования, независимо от наличия веб-сервера (IIS), базы данных или внешних классов. (Это отличается от модульного тестирования страниц веб-форм, для которых требуется веб-сервер.)

В этом пошаговом руководстве тесты для контроллера MVC будут созданы до реализации его функциональности. Тесты можно писать при отсутствии контроллера. Преимущество заключается в том, что ошибки компилятора в ваших модульных тестах будут представлять собой первый уровень ошибок модульного тестирования. Внимание уделяется процессу разработки контроллера посредством написания модульных тестов до реализации контроллера, что является важным аспектом философии управляемой тестами разработки. (Хороший обзор философии управляемой тестами разработки MVC см. на странице It’s Not TDD, It’s Design By Example в блоге Брэда Уилсона.)

В пошаговом руководстве создается проект и модульные тесты для приложения, которое используется для отображения и редактирования контактов. Контакт содержит имя, фамилию, номер телефона и псевдоним электронной почты.

Для этого раздела доступен проект Visual Studio с исходным кодом (загрузить). Загружаемые файлы содержат завершенные проекты ASP.NET MVC C# и Visual Basic (папки cs и vb), а также проекты C# и Visual Basic в папке QuickStart. В первой части этого пошагового руководства описываются процессы создания проекта MVC в Visual Studio, добавления модели данных и добавления метаданных в модель данных. Если вы знаете, как создать проект ASP.NET MVC и модель данных Entity Framework, воспользуйтесь проектами, которые находятся в папке QuickStart загружаемого проекта, и перейдите к разделу Добавление репозитория данного пошагового руководства.

Для выполнения этого пошагового руководства потребуется следующее.

  • Microsoft Visual Studio 2008 с пакетом обновления 1 (SP1) или более поздняя версия.

    ПримечаниеПримечание

    Visual Studio Standard Edition и Visual Web Developer Express не поддерживают создание проектов модульных тестов.

  • Платформа ASP.NET MVC 2. Для загрузки самой последней версии платформы перейдите на страницу загрузки ASP.NET MVC.

  • Файл базы данных Contact.mdf. Этот файл базы данных входит в пример проекта, который можно загрузить для данного проекта (загрузить).

В данном разделе создается новое решение Visual Studio, которое содержит проект приложения и тестовый проект.

ПримечаниеПримечание

В данном пошаговом руководстве используется платформа модульного тестирования Visual Studio. Дополнительные сведения о добавлении других платформ модульного тестирования см. в разделе Пошаговое руководство. Создание простого проекта MVC с помощью модульных тестов в Visual Studio.

Создание приложения MVC с модульными тестами

  1. В меню Файл Visual Studio выберите пункт Создать проект.

  2. В диалоговом окне Создать проект в разделе Установленные шаблоны откройте узел Visual C# или Visual Basic и выберите Веб.

  3. Выберите шаблон Веб-приложение ASP.NET MVC.

  4. Назовите решение MvcContacts.

  5. Нажмите кнопку ОК.

  6. В появившемся диалоговом окне Создание проекта модульного теста проверьте, что установлен флажок Да, создать проект модульного теста, а затем нажмите кнопку ОК.

    Visual Studio создаст решение с двумя проектами — один с именем MvcContacts, а второй с именем MvcContacts.Tests.

  7. В меню Тест выберите команду Выполнить, а затем выберите Все тесты в решении.

    Результаты будут показаны в окне Результаты теста. Тесты пройдены.

  8. В проекте MvcContacts.Tests откройте и проверьте тестовый класс контроллера учетных записей (MvcContacts\MvcContacts.Tests\Controllers\AccountControllerTest) и класс модели контроллера учетных записей (MvcContacts\Models\AccountModels).

    С помощью этих классов можно получить представление о создании макетов интерфейсов и управляемой тестами разработки. Макетирование — это процесс создания простых заменяющих объектов (макетов) для зависимостей в классе, которые позволяют тестировать класс без зависимостей. Для тестирования интерфейсов обычно создается макет класса, который реализует тестируемый интерфейс. Например, класс MockMembershipService в тестовом классе контроллера учетных записей реализует интерфейс IMembershipService для макетирования членов, входящих в классы членства, таких как методы ValidateUser, CreateUser и ChangePassword. Класс MockMembershipService позволяет тестировать методы действий, которые создают учетные записи пользователей, проверяют регистрационную информацию пользователей и изменяют пароли пользователей без необходимости создания экземпляра класса членства, такого как Membership.

В этом пошаговом руководстве используется модель EDM, созданная на основе базы данных Contact из загружаемого примера проекта. (Чтобы использовать файл Contact.mdf, необходимо загрузить проект. Дополнительные сведения см. в разделе "Необходимые компоненты" ранее в этом пошаговом руководстве.)

Создание модели базы данных

  1. В обозревателе решений щелкните правой кнопкой мыши папку App_Data в проекте MvcContacts, выберите команду Добавить, а затем щелкните Существующий элемент.

    Откроется диалоговое окно Добавление существующего элемента.

  2. Перейдите в папку, которая содержит файл Contact.mdf, выберите его и нажмите кнопку Добавить.

  3. В обозревателе решений щелкните правой кнопкой мыши проект MvcContacts, выберите команду Добавить и щелкните Создать элемент.

    Откроется диалоговое окно Добавление нового элемента.

  4. В разделе Установленные шаблоны откройте узел Visual C# или Visual Basic, выберите раздел Данные, а затем выберите шаблон Модель ADO.NET EDM.

  5. В поле Имя введите ContactModel и нажмите кнопку Добавить.

    Откроется окно мастера модели EDM.

  6. В разделе Что должна содержать модель? выберите Создать из базы данных и нажмите кнопку Далее.

  7. В разделе Какое соединение данных должно использовать ваше приложение для подключения к базе данных? выберите Contact.mdf.

  8. Убедитесь, что установлен флажок Сохранить настройки подключения сущности в Web.config как. Можно оставить имя строки подключения, используемое по умолчанию.

  9. Нажмите кнопку Далее.

    Мастер отображает страницу, где можно указать, какие объекты базы данных нужно включить в модель.

  10. Выберите узел Таблицы, чтобы выбрать таблицу Contacts. Можно оставить заданное по умолчанию пространство имен модели.

  11. Нажмите кнопку Готово.

    Откроется конструктор модели EDM ADO.NET. Закройте конструктор.

В этом разделе добавляются метаданные контакта. Метаданные класса контакта не используются в модульных тестах. Однако это делает пример более полным, обеспечивая автоматическую проверку данных на стороне клиента и на стороне сервера.

Добавление метаданных модели

  1. В папке MvcContacts\Models создайте новый файл класса с именем ContactMD.

    В этом файле добавляется класс (ContactMD), содержащий метаданные для объекта сущности Contact, который является частью используемой в данном пошаговом руководстве модели данных.

  2. Замените код в файле следующим кодом:

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

При работе с шаблоном MVC рекомендуется не включать модель EDM или какой-либо другой код платформы доступа к данным в свой контроллер. Вместо этого следует использовать шаблон репозитория. Репозиторий располагается между приложением и хранилищем данных. Репозиторий отделяет бизнес-логику от взаимодействий с основной базой данных и сосредотачивает доступ к данным в одном месте, что облегчает создание данных и управление ими.

Репозиторий возвращает объекты из модели домена. Для простых моделей, таких как модель, с которой вы работаете в данном пошаговом руководстве, объекты возвращаются из модели EDM, LINQ to SQL и других моделей данных, являющихся объектами домена.

Для более сложных приложений может потребоваться уровень сопоставления. Уровень сопоставления не обязательно должен быть неэффективным. Поставщики LINQ могут создавать эффективные запросы к конечному хранилищу данных (то есть они могут выполнять запрос с помощью минимального числа промежуточных объектов).

Работа с репозиторием не требует знаний модели EDM, LINQ to SQL или какой-либо другой используемой модели данных. (Хотя LINQ не описывается в данном пошаговом руководстве, использование LINQ в качестве абстракции запросов означает, что механизм хранения данных может быть скрыт. Например, это позволяет использовать SQL Server в рабочей среде, а в процессе тестирования для коллекций в памяти — LINQ to Objects.)

Для тестирования методов действий в контроллере, который напрямую обращается к модели EDM, необходимо подключение к базе данных, поскольку методы действий зависят от модели EDM (которая зависит от базы данных). В следующем коде представлен контроллер MVC, который напрямую обращается к сущности Contact модели EDM, а также показан простой пример, почему смешанные вызовы базы данных в методах действий затрудняют тестирование этих методов. Например, модульные тесты, изменяющие и удаляющие данные, изменяют состояние базы данных. Для этого требуется, чтобы при каждом проходе модульных тестов имелась чистая установка базы данных. Кроме того, вызовы базы данных весьма ресурсоемкие, в то время как модульные тесты должны быть достаточно простыми, чтобы их можно было часто выполнять в процессе разработки приложения.


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


Использование шаблона репозитория дает следующие преимущества:

  • он предоставляет подстановочную точку для модульных тестов. Можно тестировать бизнес-логику без базы данных и других внешних зависимостей;

  • повторяющиеся запросы и шаблоны доступа к данным могут быть удалены и подвергнуты рефакторингу в репозитории;

  • методы контроллера могут использовать строго типизированные параметры. Это значит, что компилятор ищет ошибки типов данных каждый раз во время компиляции, а не во время выполнения процесса тестирования;

  • централизованный доступ к данным, который дает следующие преимущества:

    • высокая степень разделения областей ответственности — еще один принцип MVC, который повышает удобство обслуживания и удобочитаемость;

    • упрощенная реализация централизованного кэширования данных;

    • более гибкая и менее связанная архитектура, которую можно адаптировать в качестве общей структуры по мере развития приложения;

  • реакция на событие может быть связана с соответствующими данными. Например, можно вычислять поля или применять сложные связи и бизнес-правила к элементам данных в сущности;

  • модель домена можно применять для упрощения сложной бизнес-логики.

Чтобы использовать шаблон репозитория в шаблоне MVC и процессе управляемой тестами разработки, обычно необходимо создать интерфейс для своего класса доступа к данным. Интерфейс репозитория позволит легко внедрить макет репозитория во время модульного тестирования методов контроллера.

В этом разделе добавляется репозиторий контактов, который является классом, используемым для сохранения контактов в базе данных. А также добавляется интерфейс для репозитория контактов.

Добавление репозитория

  1. В папке MvcContacts\Models создайте файл класса и добавьте класс с именем IContactRepository.

    Класс IContactRepository будет содержать интерфейс объекта репозитория.

  2. Замените код в файле класса следующим кодом:

    
    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. В папке MvcContacts\Models создайте новый класс с именем EntityContactManagerRepository.

    Класс EntityContactManagerRepository реализует интерфейс IContactRepository объекта репозитория.

  4. Замените код в классе EntityContactManagerRepository следующим кодом:

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

В этом разделе будет добавлен макет реализации репозитория и модульные тесты, а также будут реализованы функциональные возможности приложения на основании модульных тестов.

Реализация репозитория в памяти

  1. В проекте MvcContacts.Tests создайте папку Models.

  2. В папке MvcContacts.Tests\Models создайте новый класс с именем InMemoryContactRepository.

    Класс InMemoryContactRepository реализует интерфейс IContactRepository, который был создан ранее и у которого имеется простой репозиторий для управления разработкой приложения.

  3. Замените код в классе InMemoryContactRepository следующим кодом:

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

Добавление поддержки тестирования

  1. В проекте MvcContacts откройте файл Controllers\HomeController.cs. В файле Controllers\HomeController.cs замените существующий код следующим кодом:

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

    <HandleError()> _
    Public Class HomeController
        Inherits System.Web.Mvc.Controller
    
        Private _repository As IContactRepository
    
        Public Sub New()
            Me.New(New EF_ContactRepository())
        End Sub
    
        Public Sub New(ByVal repository As IContactRepository)
            _repository = repository
        End Sub
    
        Public Function Index() As ViewResult
            Throw New NotImplementedException()
            Return View()
        End Function
    End Class
    
    

    Этот класс содержит два конструктора. Один из них является конструктором без параметров. Другой использует параметр типа IContactRepository. Этот конструктор будет использоваться модульными тестами для передачи макета репозитория. Конструктор без параметров создает экземпляр класса EF_ContactRepository и вызывается конвейером MVC при вызове метода действий в контроллере.

  2. Закройте файл HomeController.

  3. В проекте MvcContacts.Test откройте файл Controllers\HomeControllerTest и замените код в файле следующим кодом:

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

    Imports System
    Imports System.Security
    Imports System.Security.Principal
    Imports System.Web
    Imports System.Web.Mvc
    Imports System.Web.Routing
    Imports System.Web.Security
    Imports Microsoft.VisualStudio.TestTools.UnitTesting
    Imports MvcContacts
    
    <TestClass()> Public Class HomeControllerTest
    
    
    
    
       <TestMethod()>
        Public Sub Index_Get_AsksForIndexView()
            ' Arrange
            Dim controller = GetHomeController(New InMemoryContactRepository())
            ' Act
            Dim result As ViewResult = controller.Index()
            ' Assert
            Assert.AreEqual("Index", result.ViewName)
        End Sub
        ' </snippet8>
    
        Private Function GetContactID_1() As Contact
            Return GetContactNamed(1, "Janet", "Gates")
        End Function
    
        Private Function GetContactNamed(ByVal id As Integer, ByVal fName As String, ByVal lName As String) As Contact
            Return New Contact With {.Id = id,
                                     .FirstName = fName,
                                     .LastName = lName,
                                     .Phone = "710-555-0173",
                                     .Email = "janet1@adventure-works.com"}
    
        End Function
    
        Private Shared Function GetHomeController(ByVal repository As IContactRepository) As HomeController
            Dim controller As New HomeController(repository)
    
            controller.ControllerContext = New ControllerContext() With
                                           {.Controller = controller,
                                             .RequestContext = New RequestContext(New MockHttpContext(),
                                            New RouteData())}
            Return controller
        End Function
    
        Private Class MockHttpContext
            Inherits HttpContextBase
    
            Private ReadOnly _user As IPrincipal = New GenericPrincipal(New GenericIdentity("someUser"), Nothing)
    
            Public Overrides Property User() As IPrincipal
                Get
                    Return _user
                End Get
                Set(ByVal value As System.Security.Principal.IPrincipal)
                    MyBase.User = value
                End Set
            End Property
        End Class
       
    End Class
    
    

    Код в предыдущем примере содержит два перегруженных метода, которые получают контакт (GetContact), и метод для получения объекта HomeController. Модульные тесты будут вызывать методы в объекте HomeController. Код также содержит класс для макета объекта HttpContext. Класс MockHttpContext, используемый в этом примере, является упрощенной версией данного класса из файла класса AcountControllerTest, который был создан на этапе создания нового проекта MVC с модульными тестами.

Один из принципов управляемой тестами разработки для шаблона MVC — каждый тест должен управлять определенным требованием в методе действий. Тест не должен проверять базу данных или другие компоненты (однако эти компоненты должны быть протестированы в модульных тестах доступа к данным и в процессе интеграционного тестирования). Еще один принцип — имена тестов должны быть максимально содержательными. Короткие общие имена наподобие Creat_Post_Test1 могут затруднить понимание тестов, когда их число превысит сотню.

В этой части пошагового руководства разрабатываются вызовы метода контроллера Home по умолчанию, обеспечивающие возврат списка контактов. По умолчанию для контроллера используется метод Index, таким образом первый тест проверит, возвращает ли контроллер представление индекса. Метод Index был изменен ранее в этом пошаговом руководстве для обеспечения возврата объекта ViewResult, а не общего объекта ActionResult. Если известно, что метод будет всегда возвращать объект ViewResult, можно упростить модульные тесты, возвращая объект ViewResult из метода контроллера. Если возвращается объект ViewResult, в модульном тесте нет необходимости приводить объект типа ActionResult к объекту ViewResult, что упрощает тесты и делает их более удобочитаемыми.

Добавление первого теста

  1. В классе HomeControllerTest добавьте модульный тест с именем Index_Get_AsksForIndexView, который проверяет, что метод Index возвращает представление с именем Index.

    В следующем примере показан завершенный модульный тест.

    
    [TestMethod]
    public void Index_Get_AsksForIndexView() {
        // Arrange
        var controller = GetHomeController(new InMemoryContactRepository());
        // Act
        ViewResult result = controller.Index();
        // Assert
        Assert.AreEqual("Index", result.ViewName);
    } 
    
    
    
  2. В меню Тест выберите команду Выполнить, а затем выберите Все тесты в решении.

    Результаты будут показаны в окне Результаты теста. Как и предполагалось, модульный тест Index_Get_AsksForIndexView не пройден.

  3. Реализуйте следующий метод Index класса HomeController для возврата списка всех контактов.

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

    В духе управляемой тестами разработки пишется только код, который требуется для прохождения теста разработки.

  4. Запустите тесты. На этот раз модульный тест Index_Get_AsksForIndexView пройден.

В этом разделе проверяется, существует ли возможность извлечения всех контактов. Создавать модульные тесты для тестирования доступа к данным не требуется. Проверка приложения на возможность доступа к базе данных и извлечения контактов важна, но это относится к интеграционному тесту, а не к модульному тесту управляемой тестами разработки.

Добавление теста для извлечения контактов

  • Создайте тест, который добавляет два контакта в репозиторий в памяти для класса HomeControllerTest, а затем проверяет, находятся ли они в объекте ViewDataModel, который содержится в представлении Index.

    Ниже приведен пример завершенного теста.

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

Теперь можно протестировать процесс создания нового контакта. Первый тест проверяет, успешно ли выполнена операция HTTP POST, и вызывает метод Create, используя данные, в которых преднамеренно содержатся ошибки модели. В результате новый контакт не будет добавлен, а вместо этого операция HTTP GET вернет представление Create, содержащее введенные поля и ошибки модели. Выполнение модульного теста для контроллера не приводит к запуску конвейера MVC или процесса привязки модели. Поэтому ошибка модели не будет перехвачена в процессе привязки. С учетом этого в тест добавляется ошибка макета.

Добавление теста для проверки процесса создания контакта

  1. Добавьте в проект следующий тест:

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

    В коде показана попытка добавления контакта, содержащего ошибки модели, которая для операции HTTP GET возвращает представление Create.

  2. Добавьте следующий тест:

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

    В примере кода показано, как проверить, что операция HTTP POST для метода Create добавляет допустимый контакт в репозиторий.

Часто не принимается во внимание тест, который проверяет, правильно ли методы обрабатывают исключения. Класс InMemoryContactRepository позволяет задать макет исключения, имитирующий исключение, которое произойдет в базе данных при возникновении ограничения или другого нарушения. Большинство исключений базы данных не будет перехвачено в процессе проверки модели, поэтому важно проверить, что код обработки исключений работает правильно. Следующий пример показывает, как это сделать.


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


Загружаемый пример содержит еще ряд тестов, которые здесь не описаны. Чтобы узнать больше об использовании макетов объектов и методологии управляемой тестами разработки в проектах MVC, просмотрите дополнительные тесты и напишите тесты для методов Delete и Edit.

Показ: