c# – 如何单元测试使用MVC ASP.Net返回正确的视图?

前端之家收集整理的这篇文章主要介绍了c# – 如何单元测试使用MVC ASP.Net返回正确的视图?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我是MVC,单元测试,模拟和TDD的新手.我尽量遵循最佳做法.

我为控制器编写了单元测试,如果返回正确的视图,我无法测试.如果我使用ViewResult.ViewName,如果控制器中没有指定视图名称,则测试总是失败.如果我在控制器中指定了ViewName,测试总是通过,即使视图不存在.

我也试过测试Response.Status代码,但这总是返回200(代码取自Darin Dimitrov的答案为MVC3 unit testing response code).我的目标是经典的红色,绿色重构,当创建一个新的视图,避免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}",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_NAME,expectedViewName);
    }

    [Test]
    [TestCase("Index","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);
    }
}

解决方法

I’m new to MVC,Unit Testing,Mocking and TDD. I’m trying to follow best practice as closely as possible.

我感到开心,越来越多的开发人员开始为他们的代码编写单元测试,所以恭喜你走在正确的道路上.

if I don’t specify the view name in the controller. If I do specify the ViewName in the controller the test always passes,even if the view doesn’t exist.

当您在View方法中未指定视图名称时,将指示MVC引擎呈现默认视图,例如

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

上面的代码将返回一个空的视图名,这意味着渲染的视图将是操作的名称,在这种情况下它将是Index.

因此,如果要测试一个操作返回默认视图,则必须测试返回的视图名称是否为空

Test always passes as view name is specified even if the view doesn’t exist.

为了解释这里发生了什么,我将首先解释动作过滤器的工作原理.

基本上有四种类型的滤波器

>异常过滤器
>授权过滤器
>动作过滤器
>结果过滤器

我会专注于行动和结果过滤器

动作过滤器由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以将其发送回客户端,这是处理结果时

这种干涉分离的关键是美观和关键,允许我们单位测试我们的控制器的行动,否则,如果它们耦合,我们将无法单独测试单独测试结果的行动

现在,RazorViewEngine尝试在执行操作后(当处理结果时)找到一个视图,这就是为什么您的测试返回true,即使物理视图不存在.这是预期的行为,并记住您需要隔离测试控制器的操作.只要您在单元测试中断言预期视图呈现,您就可以完成单元测试.

如果您想声明物理视图存在,那么您将会讨论一些特定的集成测试:功能测试或用户验收测试 – 这些测试需要使用浏览器实例化您的应用程序,而不是单元测试

现在您可以手动编写单元测试(如果您进入单元测试世界,这是一个很好的练习),但是,我想推荐一些可以帮助您编写单元的MVC测试框架测试真的很快

> https://github.com/robdmoore/FluentMVCTesting
> http://mvccontrib.codeplex.com/wikipage?title=TestHelper&referringTitle=Documentation

关于这些框架的一些个人评论

根据我的经验,MVC Contrib具有比Fluent MVC测试更多的功能,由于我使用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");

我希望这有助于=)快乐的编码!

猜你在找的C#相关文章