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

The*_*thy 11 c# tdd asp.net-mvc unit-testing

我是MVC,单元测试,模拟和TDD的新手.我试图尽可能地遵循最佳实践.

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

我也尝试过测试Response.Status代码但是这总是返回200(代码取自Darin Dimitrov对MVC3单元测试响应代码的回答).我的目标是创建新视图时经典的红色,绿色重构,并在上线时避免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);
    }
}
Run Code Online (Sandbox Code Playgroud)

Jup*_*aol 27

我是MVC,单元测试,模拟和TDD的新手.我试图尽可能地遵循最佳实践.

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

如果我没有在控制器中指定视图名称.如果我在控制器中指定ViewName,则测试始终通过,即使视图不存在也是如此.

如果未在View方法中指定视图名称,则指示MVC引擎呈现默认视图,例如

public ActionResult Index() { return View(); }
Run Code Online (Sandbox Code Playgroud)

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

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

即使视图不存在,测试也始终在指定视图名称时传递.

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

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

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

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

使用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);
}
Run Code Online (Sandbox Code Playgroud)

结果过滤器使用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);
}
Run Code Online (Sandbox Code Playgroud)

执行控制器操作时,将按以下特定顺序执行以下过滤器:

IActionFilter.OnActionExecuting
IActionFilter.OnActionExecuted
IResultFilter.OnResultExecuting
IResultFilter.OnResultExecuted
Run Code Online (Sandbox Code Playgroud)

执行操作时,另一个组件负责处理ActionResult从您的操作返回的内容并呈现正确的HTML以将其发送回客户端,这是处理结果的时间

这种关注点的清晰分离是美丽和关键,允许我们对控制器的操作进行单元测试,否则,如果它们被耦合,我们将无法单独测试操作的结果

现在RazorViewEngine尝试在执行操作后(当处理结果时)查找视图,这就是即使物理视图不存在,测试也会返回true的原因.这是预期的行为,并且记住您需要单独测试控制器的操作.只要您在单元测试断言预期视图已呈现,您就完成了单元测试.

如果你想断言物理视图存在那么你会谈论一些特定的集成测试:功能测试或用户验收测试 - 这类测试需要使用浏览器实例化你的应用程序它们不是任何单元测试

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

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

根据我的经验,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");
Run Code Online (Sandbox Code Playgroud)

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