2013-11-15 3 views
1

У меня есть HTML-помощник, который используется в базовой системе CMS, которая вызывает контроллер и действие для конкретного объекта контента. Ниже приведен пример сокращения:Как я могу протестировать HTML-помощник, который выполняет вызов действия?

public static MvcHtmlString RenderCMSObject(this HtmlHelper helper, CMSObject cmsObject) 
{ 
    var actionName = string.IsNullOrEmpty(cmsObject.ActionName) 
     ? "Index" 
     : cmsObject.ActionName; 

    var controllerName = string.IsNullOrEmpty(cmsObject.ControllerName) 
     ? "Default" 
     : cmsObject.ControllerName; 

    return helper.Action(actionName, controllerName); 
} 

Как я могу это проверить?

Я могу создать HtmlHelper и высмеять некоторые его аспекты (например, ViewBag) для проверки HTML-помощников, которые возвращают строки (например).

Для приведенного выше примера просто требуется тест, который может высмеять метод Action, чтобы возвращать что-то в частности, поэтому можно утверждать, что он вызывается с правильными параметрами.

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

+0

Какое поведение вы испытываете? –

+0

Что HTML-помощник выполнит действие с правильным именем действия и именем контроллера. Фактический вспомогательный метод более сложный, чем пример, но я упростил его, чтобы показать только область, требующую утверждения. – makit

+0

Обычно проверяются модульные тесты для проверки некоторого поведения, отличного от простого прохождения параметров. Вот почему мы обычно не тестируем автоматические свойства; мы просто предполагаем, что они работают. Тесты интеграции будут охватывать это. –

ответ

2

Нет простого способа проверить хелпер.Акция, вызвала ли она правильные параметры. Это связано с тем, что, поскольку некоторые из замечаний указывают на то, что HtmlHelper не является насмешливым, а также действием, которое также является статическим расширением. НО вы можете использовать обходной тест определенного сценария, например, согласно вашему вопросу.

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

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

Это просто руководство, и я не полностью проверил свой код. Я знаю, что это будет работать в тестовом контексте, но я не совсем уверен, что это приведет к правильному результату во время выполнения производственного кода. Если нет, я с удовольствием посмотрю на это снова.

Сначала я бы создал класс, который позволяет мне переопределить метод helper.Action.

public class HtmlHelperActionInvoker { 
    public virtual MvcHtmlString InvokeAction(HtmlHelper helper, string action, string controller)  { 
     return helper.Action(action, controller); 
    } 
} 

Затем в Html вспомогательный класс Я хотел бы добавить статический конструктор и делегат/FUNC, который позволяет мне установить свою собственную версию HtmlHelpActionInvoker во время выполнения теста.

public static class SomeHtmlHelperClass 
{ 
    static SomeHelperClass() { 
     HtmlHelperActionFunc =() => new HtmlHelperActionInvoker(); 
    } 

    public static Func<HtmlHelperActionInvoker> HtmlHelperActionFunc { get; set; } 

    public static MvcHtmlString RenderCMSObject(this HtmlHelper helper, CMSObject cmsObject) 
    { 
     var actionName = string.IsNullOrEmpty(cmsObject.ActionName) 
      ? "Index" 
      : cmsObject.ActionName; 

     var controllerName = string.IsNullOrEmpty(cmsObject.ControllerName) 
      ? "Default" 
      : cmsObject.ControllerName; 

     var helperAction = HtmlHelperActionFunc(); 
     return helperAction.InvokeAction(helper, actionName, controllerName); 
    } 
    } 

Я считаю, что статический ctro будет выполнять один раз и установить экземпляр HtmlHelperActionInvoker один раз во время выполнения producion кода. Последующие запросы будут повторно использовать этот экземпляр. (Как я уже говорил прежде, чем Вы, вероятно, нужно проверить это больше)

Теперь Unit Test

Мы будем использовать технику, называемую Extract & Override.

В тестовой области создайте тестовую версию HtmlHelperActionInvoker. Это НЕ поддельный объект/заглушка/макет. Это проверенная версия HtmlHelperActionInvoker. И использовать только метод, который был вызван с ожидаемыми параметрами.

public class TesatableHtmlHelperAction : HtmlHelperActionInvoker 
{ 
    public string Controller { get; set; } 
    public string Action { get; set; } 
    public override MvcHtmlString InvokeAction(HtmlHelper helper, string action, string controller) { 
     Action = action; 
     Controller = controller; 

     return new MvcHtmlString(""); 
    } 
} 

В модульном тесте мы установили бы тестируемый TesatableHtmlHelperAction так SUT (System Test меньше) будет выполнять проверяемую версию. (Примечания: по-прежнему рассматривается как реальные, как мы переопределять поведение реального HtmlHelperActionInvoker)

[TestMethod] 
    public void HtmlHelperActionRenderCMSObject_Execute_EnsureInvokeActionCalledWithExpectedControlerAndActionName() 
    {    
     //Arrange 
     var fakecmsObject = new CMSObject() { ActionName = "foo", ControllerName = "bar" }; 
     var testableHtmlHelperAction = new TesatableHtmlHelperAction(); 
     SomeHelperClass.HtmlHelperActionFunc =() => testableHtmlHelperAction; 

     // Act 
     SomeHelperClass.RenderCMSObject(null, fakecmsObject); 

     // Verify 
     Assert.AreEqual<string>(fakecmsObject.ActionName, testableHtmlHelperAction.Action); 
     Assert.AreEqual<string>(fakecmsObject.ControllerName, testableHtmlHelperAction.Controller); 
    } 
+0

Спасибо, мне нравится это решение, я реализовал это и использовал его для создания аналогичного решения, но без делегатов, требующих, чтобы я опубликовал его как другой альтернативный механизм. – makit

0

После реализации ответа Спка, я придумал другую альтернативу, которая похожа, но вместо того, чтобы использовать делегат он использует синглтон и интерфейс к вызывающему. Он также использует Moq вместо расширения HtmlHelperActionInvoker.

Вспомогательный класс

Позволяет использовать статический конструктор и общественной собственности, держащего одиночки из класса INVOKER, который оборачивает вызов действий.

public static class SomeHtmlHelperClass 
{ 
    static SomeHtmlHelperClass() 
    { 
     ActionInvoker = new HtmlHelperActionInvoker(); 
    } 

    public static IHtmlHelperActionInvoker ActionInvoker { get; set; } 

    public static MvcHtmlString RenderCMSObject(this HtmlHelper helper, CMSObject cmsObject) 
    { 
     var actionName = string.IsNullOrEmpty(cmsObject.ActionName) 
      ? "Index" 
      : cmsObject.ActionName; 

     var controllerName = string.IsNullOrEmpty(cmsObject.ControllerName) 
      ? "Default" 
      : cmsObject.ControllerName; 

     return ActionInvoker.Action(helper, actionName, controllerName, cmsObject); 
    } 
} 

интерфейс Чешуи и класс

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

public interface IHtmlHelperActionInvoker 
{ 
    MvcHtmlString Action(HtmlHelper helper, string action, string controller, CMSObject model); 
} 

public class HtmlHelperActionInvoker : IHtmlHelperActionInvoker 
{ 
    public MvcHtmlString Action(HtmlHelper helper, string action, string controller, CMSObject model) 
    { 
     return helper.Action(action, controller, model); 
    } 
} 

Unit Test

Mocks заклинателя, используя Moq, чтобы вернуть строку, которая затем утверждал в конце. Выделенный класс вводится в помощник через инъекцию свойств до выполнения помощника.

[TestFixture] 
public class HelperTests 
{ 
    [Test] 
    public void GivenACmsObjectWithCompletedActionAndController_WhenRenderCMSObject_ThenExpectedActionOutcomeforActionAndControllerIsGiven() 
    { 
     const string actionName = "foo"; 
     const string controllerName = "bar"; 
     const string expectedOutcome = "<h1>Bruce</h1>"; 

     // Arrange 
     var cmsObject = new CMSObject { ActionName = actionName, ControllerName = controllerName }; 

     var mockInvoker = new Mock<IHtmlHelperActionInvoker>(); 
     mockInvoker.Setup(x => x.Action(null, actionName, controllerName, cmsObject)).Returns(MvcHtmlString.Create(expectedOutcome)); 
     SomeHtmlHelperClass.ActionInvoker = mockInvoker.Object; 

     // Act 
     var result = SomeHtmlHelperClass.RenderCMSObject(null, cmsObject); 

     // Verify 
     Assert.That(result.ToString(), Is.EqualTo(expectedOutcome)); 
    } 
} 
2

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

public static GetActionNameWithDefault(CMSObject cmsObject) 
{ 
    return string.IsNullOrEmpty(cmsObject.ActionName) 
     ? "Index" 
     : cmsObject.ActionName; 
} 

public static GetControllerNameWithDefault(CMSObject cmsObject) 
{ 
    return string.IsNullOrEmpty(cmsObject.ControllerName) 
     ? "Default" 
     : cmsObject.ControllerName 
} 

public static MvcHtmlString RenderCMSObject(this HtmlHelper helper, CMSObject cmsObject) 
{ 
    return helper.Action(
     helper, 
     GetActionNameWithDefault(cmsObject), 
     GetControllerNameWithDefault(cmsObject)); 
} 
+0

Это хороший момент, я думаю, что это был бы лучший вариант для простого помощника.Было бы проблемой, если бы помощник был немного более сложным, хотя, например, иметь помощника, который принимает список объектов и вызывает действие на них всех и возвращает как одну MvcHtmlString. Наличие опции, позволяющей высмеивать вспомогательное действие, делает его гибким для большего количества случаев использования. – makit

Смежные вопросы