2010-03-13 5 views
20

У меня есть контроллер, который реализует простую операцию Добавить на сущность и перенаправляет на страницу Детали:Модульное тестирование контроллера в ASP.NET MVC 2 с RedirectToAction

[HttpPost] 
public ActionResult Add(Thing thing) 
{ 
    // ... do validation, db stuff ... 
    return this.RedirectToAction<c => c.Details(thing.Id)); 
} 

Это прекрасно работает (с использованием RedirectToAction от сборка MvcContrib).

Когда я тестирую этот метод, я хочу получить доступ к представлению ViewData, которое возвращается из действия Details (так что я могу получить первичный ключ вновь вставленной вещи и доказать, что он теперь находится в базе данных).

Испытание:

var result = controller.Add(thing); 

Но результат здесь типа: System.Web.Mvc.RedirectToRouteResult (который является System.Web.Mvc.ActionResult). Он еще не выполнил метод Details.

Я попытался позвонить ExecuteResult на возвращаемом объекте, проходящем в издеваемом ControllerContext, но структура была недовольна отсутствием деталей в издеваемом объекте.

Я мог бы попробовать заполнить детали и т. Д., Но тогда мой тестовый код намного длиннее кода, который я тестирую, и я чувствую, что мне нужны модульные тесты для модульных тестов!

Я что-то упустил в философии тестирования? Как проверить это действие, когда я не могу получить его возвращенное состояние?

ответ

7

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

Нечто подобное (используя приближенный синтаксис для Rhino.Mocks & NUnit):

[Test] 
public void Add_SavesThingToDB() 
{ 
    var dbMock = MockRepository.GenerateMock<DBService>(); 
    dbMock.Expect(x => x.Save(thing)).Repeat.Once(); 

    var controller = new MyController(dbMock); 
    controller.Add(new Thing()); 

    dbMock.VerifyAllExpectations(); 
} 

[Test] 
public void Add_RedirectsAfterSave() 
{ 
    var dbMock = MockRepository.GenerateMock<DBService>(); 

    var controller = new MyController(dbMock); 
    var result = (RedirectToRouteResult)controller.Add(new Thing()); 

    Assert.That(result.Url, Is.EqualTo("/mynew/url")); 
} 
+0

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

+0

Ну, определяя масштаб и усилие, чтобы сдавать тесты, что тестировать и т. Д., Я тоже борюсь. Я думаю, вы должны попытаться разделить свои тесты на две категории: модульные тесты и интеграционные тесты. В модульных тестах должны тестироваться только очень маленькие единицы функциональности, такие как тесты выше. Интеграционные тесты должны смотреть на то, как все интегрируется, возможно, охватывая небольшую историю пользователей, которая у вас есть. Я бы предпочел сделать интеграционные тесты как можно ближе к «реальному» использованию, например, запустить WatiN и на самом деле щелкнуть пару ссылок. Не нужно вообще насмехаться над этим. – rmac

+0

Если вы протестируете свой DBService, убедитесь, что он работает. Затем вы должны предположить, что он может, и он будет обрабатывать вызовы базы данных правильно. Поэтому, если ваш контроллер использует эту услугу, вы знаете, что она будет работать. С помощью фальшивой структуры вы можете проверить параметры, которые передаются методам службы, и этого достаточно. Я думаю, что rmacfie прав, возможно, вы пытаетесь проверить слишком много глубже. Ваш модульный тест должен охватывать только одно действие, а не весь процесс. –

12

Существует MVC Contrib TestHelper, которые являются фантастическими для тестирования большинства ActionResult

Вы можете получить его здесь: http://mvccontrib.codeplex.com/wikipage?title=TestHelper

Ниже приведен пример синтаксиса:

var controller = new TestController(); 

controller.Add(thing) 
      .AssertActionRedirect() 
      .ToAction<TestController>(x => x.Index()); 

Чтобы проверить, были ли данные сохранены успешно, вы можете, возможно, напрямую обратиться к своей базе данных, я не знаю, используете ли вы ORM или что-то в этом роде, но вы должны что-то сделать, чтобы получить последний элемент, базу данных, затем сравните со значением, которое вы предоставили в своем Add ActionResult, и посмотрите, нормально ли это.

Я не думаю, что тестирование ваших подробностей ДействиеПодробнее о том, сохраняются ли ваши данные, является правильный подход. Это не будет единичным тестом, более функциональным тестом.

Но вы также должны выполнить модульный тест своего метода «Сведения», чтобы убедиться, что ваш объект viewdata заполнен нужными данными, поступающими из вашей базы данных.

31

Я использую MVC2 RC2 в настоящий момент, и ответ от rmacfie не совсем сработал для меня, но я получил правильный путь.

Справедливо или нет, мне удалось сделать это в моем тесте вместо:

var actionResult = (RedirectToRouteResult)logonController.ForgotUsername(model); 

actionResult.RouteValues["action"].should_be_equal_to("Index"); 
actionResult.RouteValues["controller"].should_be_equal_to("Logon"); 

Не уверен, если это кому-то поможет, но вы можете сэкономить 10 минут.

+2

Удивительный! Аналогичным образом вы можете проверить дополнительные значения маршрута. Для 'RedirectToAction (« Подробности »,« Лицо », {personId = 123})' вы можете проверить 'personId':' Assert.AreEqual (123, actionResult.RouteValues ​​["personId"]) ' – Kirill

6

У меня есть статический вспомогательный метод, который проверяет перенаправление.

public static class UnitTestHelpers 
{ 
    public static void ShouldEqual<T>(this T actualValue, T expectedValue) 
    { 
     Assert.AreEqual(expectedValue, actualValue); 
    } 

    public static void ShouldBeRedirectionTo(this ActionResult actionResult, object expectedRouteValues) 
    { 
     RouteValueDictionary actualValues = ((RedirectToRouteResult)actionResult).RouteValues; 
     var expectedValues = new RouteValueDictionary(expectedRouteValues); 

     foreach (string key in expectedValues.Keys) 
     { 
      Assert.AreEqual(expectedValues[key], actualValues[key]); 
     } 
    } 
} 

Тогда создание теста перенаправления очень просто.

[Test] 
public void ResirectionTest() 
{ 
    var result = controller.Action(); 

    result.ShouldBeRedirectionTo(
     new 
     { 
      controller = "ControllerName", 
      action = "Index" 
     } 
    ); 
} 
+3

+1 для метода расширения, но по какой-либо причине имя контроллера MVC3 равно null –