Как я Unit Test верное представление возвращается с MVC ASP.Net?

Im new для MVC, Unit Testing, Mocking и TDD. Я стараюсь как можно ближе следовать наилучшей практике.

Я написал a unit test для контроллера, и у меня возникли проблемы с тестированием, если вернется правильный вид. Если я использую ViewResult.ViewName, тест всегда терпит неудачу, если я не укажу имя представления в контроллере. Если я задаю ViewName в контроллере, тест всегда проходит, даже если представление не существует.

Ive также попытался проверить код Response.Status, однако это всегда возвращает 200 (код, полученный от Дарина Димитрова, ответ на код ответа на модульное тестирование MVC3). То, что Im стремится к классическому красному, зеленому рефактору при создании нового представления и исключении ошибок 404 и System.InvalidOperationException при переходе в реальном времени, возможно ли это?

Код ниже.

public class BugStatusController : Controller
{
    public ActionResult Index(){
        return View(); // Test always fails as view name isn’t specified even if the correct view is returned.
    }

    public ActionResult Create(){
        return View("Create"); // Test always passes as view name is specified even if the view doesn’t exist.
    }
}

[TestFixture]
public class BugStatusTests
{    
    private ViewResult GetViewResult(Controller controller, string controllerMethodName){
        Type type = controller.GetType();
        ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes);

        object instance = constructor.Invoke(new object[] {});
        MethodInfo[] methods = type.GetMethods();

        MethodInfo methodInfo = (from method in methods
                                where method.Name == controllerMethodName
                                                    && method.GetParameters().Count() == 0
                                select method).FirstOrDefault();

        Assert.IsNotNull(methodInfo, "The controller {0} has no method called {1}", type.Name, controllerMethodName);

        ViewResult result = methodInfo.Invoke(instance, new object[] {}) as ViewResult;

        Assert.IsNotNull(result, "The ViewResult is null, controller: {0}, view: {1}", type.Name, controllerMethodName);

        return result;
    }

    [Test]
    [TestCase("Index", "Index")]
    [TestCase("Create", "Create")]
    public void TestExpectedViewIsReturned(string expectedViewName, string controllerMethodName){
        ViewResult result = GetViewResult(new BugStatusController(), controllerMethodName);

        Assert.AreEqual(expectedViewName, result.ViewName, "Unexpected view returned, controller: {0}, view: {1}", CONTROLLER_NAME, expectedViewName);
    }

    [Test]
    [TestCase("Index", "Index")]
    [TestCase("Create", "Create")]
    public void TestExpectedStatusCodeIsReturned(string expectedViewName, string controllerMethodName)
    {
        var controller = new BugStatusController();
        var request = new HttpRequest("", "http://localhost:58687/", "");
        var response = new HttpResponse(TextWriter.Null);
        var httpContext = new HttpContextWrapper(new HttpContext(request, response));
        controller.ControllerContext = new ControllerContext(httpContext, new RouteData(), controller);

        ActionResult result = GetViewResult(controller, controllerMethodName);

        Assert.AreEqual(200, response.StatusCode, "Failed to load " + expectedViewName + " Error: "  + response.StatusDescription);
    }
}

Ответ 1

Im new для MVC, Unit Testing, Mocking и TDD. Я стараюсь как можно ближе следовать наилучшей практике.

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

если я не укажу имя представления в контроллере. Если я задаю ViewName в контроллере, тест всегда проходит, даже если представление не существует.

Если вы не укажете имя вида в методе View, это даст команду MVC-движку отобразить представление по умолчанию, например,

public ActionResult Index() { return View(); }

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

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

Тест всегда проходит по мере указания имени представления, даже если представление не существует.

Чтобы объяснить, что происходит здесь, я сначала объясню, как работают фильтры действий.

Существуют в основном четыре типа фильтров.

  • Фильтры исключений
  • Фильтры авторизации
  • Фильтры действий
  • Фильтры результатов

Я сосредоточусь на фильтрах действий и результатов.

Фильтры действий определяются с помощью интерфейса IActionFilter

public interface IActionFilter
{
    // Summary:
    //     Called after the action method executes.
    //
    void OnActionExecuted(ActionExecutedContext filterContext);
    //
    // Summary:
    //     Called before an action method executes.
    //
    void OnActionExecuting(ActionExecutingContext filterContext);
}

Фильтры результатов определяются с помощью интерфейса IResultFilter

public interface IResultFilter
{
    // Summary:
    //     Called after an action result executes.
    //
    void OnResultExecuted(ResultExecutedContext filterContext);
    //
    // Summary:
    //     Called before an action result executes.
    //
    void OnResultExecuting(ResultExecutingContext filterContext);
}

При выполнении действия контроллера в этом конкретном порядке выполняются следующие фильтры:

IActionFilter.OnActionExecuting
IActionFilter.OnActionExecuted
IResultFilter.OnResultExecuting
IResultFilter.OnResultExecuted

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

Это чистое разделение проблем - это красота и ключ, чтобы позволить нам unit test наши действия с контроллером, иначе, если бы они были связаны, мы не сможем unit test изолировать результат действия

Теперь RazorViewEngine пытается найти представление после того, как действие было выполнено (когда результат обрабатывается), почему ваши тесты возвращают true, даже если физическое представление не существует. Это ожидаемое поведение и помните, что вам нужно изолировать свои действия с контроллером. Пока вы утверждаете в своих модульных тестах, что ожидаемое представление отображается, вы выполняете свои модульные тесты.

Если вы хотите утверждать, что физическое представление существует, тогда вы будете говорить о некоторых конкретных тестах интеграции: функциональных тестах или тестах приёма пользователей - для такого типа тестов требуется создание вашего приложения с помощью браузера они не являются в любом случае модульные тесты

Теперь хорошо, что вы пишете свои юнит-тесты вручную (это отличное упражнение, если вы попадаете в мир тестирования модулей), однако я бы хотел порекомендовать вам несколько структур MVC Testing, которые могут вам помочь быстро напишите свои аппаратные тесты

Несколько личных комментариев об этих инфраструктурах

Согласно моему опыту, MVC Contrib имеет больше возможностей, чем Fluent MVC Testing, однако, поскольку я использую MVC 4, мне не удалось заставить его работать в Visual Studio 2012, поэтому я использую комбинацию обоих (это является грязным обходным решением, пока я не найду более подходящий подход)

Это то, что я делаю:

var testControllerBuilder = new TestControllerBuilder(); // this is from MVC Contrib
var controller = new MoviesController(
    this.GetMock<IMovieQueryManager>().Object);

testControllerBuilder.InitializeController(controller); // this allows me to use the Session, Request and Response objects as mock objects, again this is provided by the MVC Contrib framework

// I should be able to call something like this but this is not working due to some problems with DLL versions (hell DLL's) between MVC Controb, Moq and MVC itself
// testControllerBuilder.CreateController<MoviesController>();

controller.WithCallTo(x => x.Index(string.Empty)).ShouldRenderDefaultView(); // this is using Fluent MVC Testing

// again instead of the above line I could use the MVC Contrib if it were working....
// var res = sut.Index(string.Empty);
// res.AssertViewRendered().ForView("Index");

Я надеюсь, что это поможет =) Счастливое кодирование!

Ответ 2

Еще один хороший вариант - расширения MVC для бесплатных утверждений.