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); } } 解决方法
我感到开心,越来越多的开发人员开始为他们的代码编写单元测试,所以恭喜你走在正确的道路上.
当您在View方法中未指定视图名称时,将指示MVC引擎呈现默认视图,例如 public ActionResult Index() { return View(); } 上面的代码将返回一个空的视图名,这意味着渲染的视图将是操作的名称,在这种情况下它将是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); } 结果过滤器由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 关于这些框架的一些个人评论 根据我的经验,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"); 我希望这有助于=)快乐的编码! (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |