Как смоделировать модель в ASP.NET MVC? - PullRequest
3 голосов
/ 11 ноября 2010

Я сделал нестандартную модель и хочу поиздеваться над ней. Я довольно новичок в MVC и очень плохо знаком с модульным тестированием. Большинство подходов, которые я видел, создают интерфейс для класса, а затем создают макет, который реализует тот же интерфейс. Однако я не могу заставить это работать, когда фактически передаю интерфейс в View. Пример «упрощенного» примера:

Модель-

public interface IContact
{
    void SendEmail(NameValueCollection httpRequestVars);
}

public abstract class Contact : IContact
{
    //some shared properties...
    public string Name { get; set; }

    public void SendEmail(NameValueCollection httpRequestVars = null)
    {
        //construct email...
    }
}

public class Enquiry : Contact
{
    //some extra properties...
}

Просмотр-

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<project.Models.IContact>" %>

<!-- other html... -->

<td><%= Html.TextBoxFor(model => ((Enquiry)model).Name)%></td>

1009 * эксплуатации контроллеров *

    [HttpPost]
    public ActionResult Index(IContact enquiry)
    {
        if (!ModelState.IsValid)
            return View(enquiry);

        enquiry.SendEmail(Request.ServerVariables);
        return View("Sent", enquiry);
    }

Модульное тестирование -

    [Test]
    public void Index_HttpPostInvalidModel_ReturnsDefaultView()
    {
        Enquiry enquiry = new Enquiry();
        _controller.ModelState.AddModelError("", "dummy value");

        ViewResult result = (ViewResult)_controller.Index(enquiry);

        Assert.IsNullOrEmpty(result.ViewName);
    }

    [Test]
    public void Index_HttpPostValidModel_CallsSendEmail()
    {
        MockContact mock = new MockContact();

        ViewResult result = (ViewResult)_controller.Index(mock);

        Assert.IsTrue(mock.EmailSent);
    }

public class MockContact : IContact
{
    public bool EmailSent = false;

    void SendEmail(NameValueCollection httpRequestVars)
    {
        EmailSent = true;
    }
}

При HttpPost я получаю исключение «Не удается создать экземпляр интерфейса». Мне кажется, что я не могу взять свой торт (передать модель) и съесть его (пройти макет для юнит-тестирования) Может быть, есть лучший подход к моделям модульного тестирования, привязанным к представлениям?

спасибо,

Мед

Ответы [ 3 ]

9 голосов
/ 11 ноября 2010

Я собираюсь выбросить это там, если вам нужно высмеивать ваши модели, вы делаете это неправильно. Ваши модели должны быть тупыми имущественными сумками.

Нет абсолютно никаких причин, по которым ваша модель должна иметь метод SendEmail. Это функциональность, которая должна вызываться из контроллера, вызывающего службу EmailService.

Отвечая на ваш вопрос:

После многих лет работы с шаблонами Разделения Концернов (SOC), такими как MVC, MVP, MVVM и просмотром статей от людей, которые меня ярче (хотелось бы найти тот, о котором я подумаю, но, возможно, я прочитал его в журнал). В конечном итоге в корпоративном приложении вы получите 3 различных набора объектов модели.

Раньше я очень любил заниматься проектированием, управляемым доменом (DDD), используя один набор бизнес-объектов, которые представляли собой как простые старые объекты c # (POCO), так и Persistent Ignorant (PI). Наличие моделей доменов, которые являются POCO / PI, оставляет вас с чистого листа объектов, где нет кода, связанного с доступом к хранилищу объектов, или имеющих другие атрибуты, которые имеют схематическое значение только для 1 области кода.

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

Чтобы устранить несоответствия импеданса View, Domain и Storage, вам действительно нужны 3 набора моделей. Ваши ViewModels будут точно соответствовать привязке ваших представлений, чтобы облегчить работу с пользовательским интерфейсом. Так что это часто имеет такие вещи, как добавление списка для заполнения раскрывающихся списков значениями, которые действительны для вашего вида / действия редактирования.

Посередине находятся доменные объекты, это те объекты, которые вы должны проверить в соответствии с вашими бизнес-правилами. Таким образом, вы будете отображать в / из них с обеих сторон в / из вида и в / из слоя хранения. В этих объектах вы можете прикрепить свой код для проверки. Лично я не фанат использования атрибутов и привязки логики валидации к вашим доменным объектам. Имеет смысл объединить атрибуты проверки с вашими ViewModels, чтобы воспользоваться преимуществами встроенной функциональности проверки на стороне клиента MVC.

Для проверки я бы порекомендовал использовать такую ​​библиотеку, как FluentValidation (или вашу собственную, которую нетрудно написать), которая позволяет вам отделить ваши бизнес-правила от ваших объектов. Несмотря на то, что благодаря новым функциям MVC3 вы можете выполнять удаленную проверку с обратной стороны и отображать ее на стороне клиента, эта опция позволяет обрабатывать настоящую бизнес-проверку.

Наконец-то у вас есть модели хранения. Как я уже говорил ранее, я очень усердствовал о возможности повторного использования объектов PI через все слои, поэтому в зависимости от того, как вы настраиваете долговременное хранилище, вы можете напрямую использовать ваши доменные объекты. Но если вы воспользуетесь такими инструментами, как Linq2Sql, EntityFramework (EF) и т. Д., У вас, скорее всего, будут автоматически сгенерированные модели с кодом для взаимодействия с поставщиком данных, поэтому вы захотите сопоставить свои доменные объекты с вашими персистентными объектами.

Итак, заверните все это, это будет стандартный логический поток в действиях MVC

Пользователь переходит на страницу редактирования товара

  1. EF запрашивает базу данных, чтобы получить информацию о существующем продукте, на уровне хранилища объекты данных EF отображаются на бизнес-объекты (BE), поэтому все методы уровня данных возвращают BE и не имеют внешней связи с EF объекты данных. (Таким образом, если вы когда-либо меняете поставщика данных, вам не нужно изменять ни одной строки кода, за исключением внутренней реализации)

  2. Контроллер получает Product BE и сопоставляет его с Product ViewModel (VM) и добавляет коллекции для различных параметров, которые можно установить для раскрывающихся списков

  3. Вернуться в представление (theview, ProductVM)

Пользователь редактирует продукт и отправляет форму

  1. Проверка на стороне клиента пройдена (полезно для проверки даты / проверки номера вместо того, чтобы отправлять форму для обратной связи)

  2. ProductVM сопоставляется с ProductBE, и в этот момент вы проверяете бизнес-правила по линиям ValidationFactory.Validate(ProductBE), если недействительные возвращаемые сообщения для просмотра и отмены редактирования, в противном случае продолжайте

  3. Вы передаете ProductBE в свою модель хранилища, во внутренней реализации слоя данных сопоставляете ProductBE с объектом данных продукта для EF и обновляете базу данных.

2016 edit: удалено использование Interface, поскольку разделение интересов и интерфейсов полностью ортогональны.

0 голосов
/ 11 ноября 2010

Можно использовать интерфейсы.

См .: http://mvcunity.codeplex.com/

0 голосов
/ 11 ноября 2010

Ваша проблема здесь:

public ActionResult Index(IContact enquiry)

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

Какой тип?Я не знаю.Также как и MVC.

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

public class Contact
{
    //some shared properties...
    public string Name { get; set; }

    public virtual void SendEmail(NameValueCollection httpRequestVars = null)
    {
        //construct email...
    }
}

public class MockContact
{
    //some shared properties...
    public string Name { get; set; }
    public bool EmailSent {get;private set;}

    public override void SendEmail(NameValueCollection vars = null)
    {
       EmailSent = true;
    }
}

и

public ActionResult Index(Contact enquiry)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...