From ba1c011bcbd6d20de890405af36ac911a5c2549f Mon Sep 17 00:00:00 2001 From: Youngjune Hong Date: Fri, 16 Jan 2015 15:22:04 -0800 Subject: [PATCH] [ControllerUnitTest] Make the controller class not to throw an exception for simple unit tests --- src/Microsoft.AspNet.Mvc.Core/Controller.cs | 53 +- .../ControllerTests.cs | 1 - .../ControllerUnitTestabilityTests.cs | 551 ++++++++++++++++++ 3 files changed, 594 insertions(+), 11 deletions(-) create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/Controller.cs b/src/Microsoft.AspNet.Mvc.Core/Controller.cs index 1ada02d98a..e0fd035e68 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs @@ -19,6 +19,7 @@ namespace Microsoft.AspNet.Mvc public class Controller : IActionFilter, IAsyncActionFilter, IOrderedFilter, IDisposable { private DynamicViewData _viewBag; + private ViewDataDictionary _viewData; public IServiceProvider Resolver { @@ -88,8 +89,40 @@ namespace Microsoft.AspNet.Mvc } } + /// + /// Gets or sets used by and . + /// + /// + /// By default, this property is activated when activates controllers. + /// However, when controllers are directly instantiated in user codes, this property is initialized with + /// . + /// [Activate] - public ViewDataDictionary ViewData { get; set; } + public ViewDataDictionary ViewData + { + get + { + if (_viewData == null) + { + // This should run only for the controller unit test scenarios + _viewData = + new ViewDataDictionary(new EmptyModelMetadataProvider(), + ActionContext?.ModelState ?? new ModelStateDictionary()); + } + + return _viewData; + } + set + { + if (value == null) + { + throw + new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(ViewData)); + } + + _viewData = value; + } + } public dynamic ViewBag { @@ -784,7 +817,7 @@ namespace Microsoft.AspNet.Mvc } /// - /// Updates the specified instance using values from the controller's current + /// Updates the specified instance using values from the controller's current /// and a . /// /// The type of the model object. @@ -809,7 +842,7 @@ namespace Microsoft.AspNet.Mvc } /// - /// Updates the specified instance using the and a + /// Updates the specified instance using the and a /// . /// /// The type of the model object. @@ -844,14 +877,14 @@ namespace Microsoft.AspNet.Mvc } /// - /// Updates the specified instance using values from the controller's current + /// Updates the specified instance using values from the controller's current /// and a . /// /// The type of the model object. /// The model instance to update. /// The prefix to use when looking up values in the current . /// - /// (s) which represent top-level properties + /// (s) which represent top-level properties /// which need to be included for the current model. /// A that on completion returns true if the update is successful [NonAction] @@ -882,7 +915,7 @@ namespace Microsoft.AspNet.Mvc } /// - /// Updates the specified instance using values from the controller's current + /// Updates the specified instance using values from the controller's current /// and a . /// /// The type of the model object. @@ -919,7 +952,7 @@ namespace Microsoft.AspNet.Mvc } /// - /// Updates the specified instance using the and a + /// Updates the specified instance using the and a /// . /// /// The type of the model object. @@ -927,7 +960,7 @@ namespace Microsoft.AspNet.Mvc /// The prefix to use when looking up values in the /// /// The used for looking up values. - /// (s) which represent top-level properties + /// (s) which represent top-level properties /// which need to be included for the current model. /// A that on completion returns true if the update is successful [NonAction] @@ -959,7 +992,7 @@ namespace Microsoft.AspNet.Mvc } /// - /// Updates the specified instance using the and a + /// Updates the specified instance using the and a /// . /// /// The type of the model object. @@ -1007,4 +1040,4 @@ namespace Microsoft.AspNet.Mvc { } } -} +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs index 636b7aa492..0fbdee98f4 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using System.Reflection; using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.ModelBinding; -using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Routing; using Microsoft.AspNet.Testing; #if ASPNET50 diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs new file mode 100644 index 0000000000..ed8cafd014 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs @@ -0,0 +1,551 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Xunit; + +namespace Microsoft.AspNet.Mvc +{ + // Tests Controler for the unit testability with which users can simply instantiate contollers for unit tests + public class ControllerUnitTestabilityTests + { + [Theory] + [MemberData(nameof(TestabilityViewTestData))] + public void ControllerView_InvokedInUnitTests(object model, string viewName) + { + // Arrange + var controller = new TestabilityController(); + + // Act + var result = controller.View_Action(viewName, model); + + // Assert + Assert.NotNull(result); + + var viewResult = Assert.IsType(result); + Assert.Equal(viewName, viewResult.ViewName); + Assert.NotNull(viewResult.ViewData); + Assert.Same(model, viewResult.ViewData.Model); + Assert.Same(controller.ViewData, viewResult.ViewData); + + if (model != null) + { + Assert.IsType(model.GetType(), viewResult.ViewData.Model); + Assert.NotNull(viewResult.ViewData.Model); + } + } + + [Theory] + [MemberData(nameof(TestabilityViewTestData))] + public void ControllerPartialView_InvokedInUnitTests(object model, string viewName) + { + // Arrange + var controller = new TestabilityController(); + + // Act + var result = controller.PartialView_Action(viewName, model); + + // Assert + Assert.NotNull(result); + + var viewResult = Assert.IsType(result); + Assert.Equal(viewName, viewResult.ViewName); + Assert.NotNull(viewResult.ViewData); + Assert.Same(model, viewResult.ViewData.Model); + Assert.Same(controller.ViewData, viewResult.ViewData); + + if (model != null) + { + Assert.IsType(model.GetType(), viewResult.ViewData.Model); + Assert.NotNull(viewResult.ViewData.Model); + } + } + + [Theory] + [MemberData(nameof(TestabilityContentTestData))] + public void ControllerContent_InvokedInUnitTests(string content, string contentType, Encoding encoding) + { + // Arrange + var controller = new TestabilityController(); + + // Act + var result = controller.Content_Action(content, contentType, encoding); + + // Assert + Assert.NotNull(result); + + var contentResult = Assert.IsType(result); + Assert.Equal(content, contentResult.Content); + Assert.Equal(contentType, contentResult.ContentType); + Assert.Equal(encoding, contentResult.ContentEncoding); + } + + [Theory] + [InlineData("/Created_1", "CreatedBody")] + [InlineData(null, null)] + public void ControllerCreated_InvokedInUnitTests(string uri, string content) + { + // Arrange + var controller = new TestabilityController(); + + // Act + var result = controller.Created_Action(uri, content); + + // Assert + Assert.NotNull(result); + + var createdResult = Assert.IsType(result); + Assert.Equal(uri, createdResult.Location); + Assert.Equal(content, createdResult.Value); + Assert.Equal(201, createdResult.StatusCode); + } + + [Theory] + [InlineData("CreatedBody", "text/html", "Created.html")] + [InlineData(null, null, null)] + public void ControllerFileContent_InvokedInUnitTests(string content, string contentType, string fileName) + { + // Arrange + var controller = new TestabilityController(); + + // Act + var result = controller.FileContent_Action(content, contentType, fileName); + + // Assert + Assert.NotNull(result); + + var fileContentResult = Assert.IsType(result); + Assert.Equal(contentType, fileContentResult.ContentType); + Assert.Equal(fileName ?? string.Empty, fileContentResult.FileDownloadName); + + if (content == null) + { + Assert.Null(fileContentResult.FileContents); + } + else + { + Assert.Equal(content, Encoding.UTF8.GetString(fileContentResult.FileContents)); + } + } + + [Theory] + [InlineData("CreatedBody", "text/html", "Created.html")] + [InlineData(null, null, null)] + public void ControllerFileStream_InvokedInUnitTests(string content, string contentType, string fileName) + { + // Arrange + var controller = new TestabilityController(); + + // Act + var result = controller.FileStream_Action(content, contentType, fileName); + + // Assert + Assert.NotNull(result); + + var fileStreamResult = Assert.IsType(result); + Assert.Equal(contentType, fileStreamResult.ContentType); + Assert.Equal(fileName ?? string.Empty, fileStreamResult.FileDownloadName); + + if (content == null) + { + Assert.Null(fileStreamResult.FileStream); + } + else + { + using (var stream = new StreamReader(fileStreamResult.FileStream, Encoding.UTF8)) + { + Assert.Equal(content, stream.ReadToEnd()); + } + } + } + + [Fact] + public void ControllerJson_InvokedInUnitTests() + { + // Arrange + var controller = new TestabilityController(); + var model = new MyModel() { Property1 = "Property_1" }; + + // Act + var result = controller.Json_Action(model); + + // Assert + Assert.NotNull(result); + + var jsonResult = Assert.IsType(result); + Assert.NotNull(jsonResult.Value); + Assert.Same(model, jsonResult.Value); + Assert.IsType(model.GetType(), jsonResult.Value); + + // Arrange + controller = new TestabilityController(); + + // Act + result = controller.Json_Action(null); + + // Assert + Assert.NotNull(result); + + jsonResult = Assert.IsType(result); + Assert.Null(jsonResult.Value); + } + + [Fact] + public void ControllerHttpNotFound_InvokedInUnitTests() + { + // Arrange + var controller = new TestabilityController(); + + // Act + var result = controller.HttpNotFound_Action(); + + // Assert + Assert.NotNull(result); + + var httpNotFoundResult = Assert.IsType(result); + Assert.Equal(404, httpNotFoundResult.StatusCode); + } + + [Fact] + public void ControllerHttpBadRequest_InvokedInUnitTests() + { + // Arrange + var controller = new TestabilityController(); + + // Act + var result = controller.HttpBadRequest_Action(); + + // Assert + Assert.NotNull(result); + + var httpBadRequest = Assert.IsType(result); + Assert.Equal(400, httpBadRequest.StatusCode); + } + + [Fact] + public void ControllerHttpBadRequestObject_InvokedInUnitTests() + { + // Arrange + var controller = new TestabilityController(); + var error = new { Error = "Error Message" }; + + // Act + var result = controller.HttpBadRequestObject_Action(error); + + // Assert + Assert.NotNull(result); + + var httpBadRequest = Assert.IsType(result); + Assert.Equal(400, httpBadRequest.StatusCode); + Assert.Same(error, httpBadRequest.Value); + + // Arrange + controller = new TestabilityController(); + + // Act + result = controller.HttpBadRequestObject_Action(null); + + // Assert + Assert.NotNull(result); + + httpBadRequest = Assert.IsType(result); + Assert.Equal(400, httpBadRequest.StatusCode); + Assert.Null(httpBadRequest.Value); + } + + [Fact] + public void ControllerCreatedAtRoute_InvokedInUnitTests() + { + // Arrange + var controller = new TestabilityController(); + var routeName = "RouteName_1"; + var routeValues = new Dictionary() { { "route", "sample" } }; + var value = new { Value = "Value_1" }; + + // Act + var result = controller.CreatedAtRoute_Action(routeName, routeValues, value); + + // Assert + Assert.NotNull(result); + + var createdAtRouteResult = Assert.IsType(result); + Assert.Equal(routeName, createdAtRouteResult.RouteName); + Assert.Single(createdAtRouteResult.RouteValues); + Assert.Equal("sample", createdAtRouteResult.RouteValues["route"]); + Assert.Same(value, createdAtRouteResult.Value); + + // Arrange + controller = new TestabilityController(); + + // Act + result = controller.CreatedAtRoute_Action(null, null, null); + + // Assert + Assert.NotNull(result); + + createdAtRouteResult = Assert.IsType(result); + Assert.Null(createdAtRouteResult.RouteName); + Assert.Empty(createdAtRouteResult.RouteValues); + Assert.Null(createdAtRouteResult.Value); + } + + [Fact] + public void ControllerCreatedAtAction_InvokedInUnitTests() + { + // Arrange + var controller = new TestabilityController(); + var actionName = "ActionName_1"; + var controllerName = "ControllerName_1"; + var routeValues = new Dictionary() { { "route", "sample" } }; + var value = new { Value = "Value_1" }; + + // Act + var result = controller.CreatedAtAction_Action(actionName, controllerName, routeValues, value); + + // Assert + Assert.NotNull(result); + + var createdAtActionResult = Assert.IsType(result); + Assert.Equal(actionName, createdAtActionResult.ActionName); + Assert.Equal(controllerName, createdAtActionResult.ControllerName); + Assert.Single(createdAtActionResult.RouteValues); + Assert.Equal("sample", createdAtActionResult.RouteValues["route"]); + Assert.Same(value, createdAtActionResult.Value); + + // Arrange + controller = new TestabilityController(); + + // Act + result = controller.CreatedAtAction_Action(null, null, null, null); + + // Assert + Assert.NotNull(result); + + createdAtActionResult = Assert.IsType(result); + Assert.Null(createdAtActionResult.ActionName); + Assert.Null(createdAtActionResult.ControllerName); + Assert.Null(createdAtActionResult.Value); + Assert.Empty(createdAtActionResult.RouteValues); + } + + [Fact] + public void ControllerRedirectToRoute_InvokedInUnitTests() + { + // Arrange + var controller = new TestabilityController(); + var routeName = "RouteName_1"; + var routeValues = new Dictionary() { { "route", "sample" } }; + + // Act + var result = controller.RedirectToRoute_Action(routeName, routeValues); + + // Assert + Assert.NotNull(result); + + var redirectToRouteResult = Assert.IsType(result); + Assert.Equal(routeName, redirectToRouteResult.RouteName); + Assert.Single(redirectToRouteResult.RouteValues); + Assert.Equal("sample", redirectToRouteResult.RouteValues["route"]); + + // Arrange + controller = new TestabilityController(); + + // Act + result = controller.RedirectToRoute_Action(null, null); + + // Assert + Assert.NotNull(result); + + redirectToRouteResult = Assert.IsType(result); + Assert.Null(redirectToRouteResult.RouteName); + Assert.Empty(redirectToRouteResult.RouteValues); + } + + [Fact] + public void ControllerRedirectToAction_InvokedInUnitTests() + { + // Arrange + var controller = new TestabilityController(); + var controllerName = "ControllerName_1"; + var actionName = "ActionName_1"; + var routeValues = new Dictionary() { { "route", "sample" } }; + + // Act + var result = controller.RedirectToAction_Action(actionName, controllerName, routeValues); + + // Assert + Assert.NotNull(result); + + var redirectToActionResult = Assert.IsType(result); + Assert.Equal(actionName, redirectToActionResult.ActionName); + Assert.Equal(controllerName, redirectToActionResult.ControllerName); + Assert.Single(redirectToActionResult.RouteValues); + Assert.Equal("sample", redirectToActionResult.RouteValues["route"]); + + // Arrange + controller = new TestabilityController(); + + // Act + result = controller.RedirectToAction_Action(null, null, null); + + // Assert + Assert.NotNull(result); + + redirectToActionResult = Assert.IsType(result); + Assert.Null(redirectToActionResult.ControllerName); + Assert.Null(redirectToActionResult.ActionName); + Assert.Empty(redirectToActionResult.RouteValues); + } + + [Fact] + public void ControllerRedirect_InvokedInUnitTests() + { + // Arrange + var controller = new TestabilityController(); + var url = "http://contoso.com"; + + // Act + var result = controller.Redirect_Action(url); + + // Assert + Assert.NotNull(result); + + var redirectResult = Assert.IsType(result); + Assert.Equal(url, redirectResult.Url); + + // Arrange + controller = new TestabilityController(); + + // Act && Assert + Assert.Throws(() => controller.Redirect_Action(null)); + } + + public static IEnumerable TestabilityViewTestData + { + get + { + yield return new object[] + { + null, + null + }; + + yield return new object[] + { + new MyModel { Property1 = "Property_1", Property2 = "Property_2" }, + "ViewName_1" + }; + } + } + + public static IEnumerable TestabilityContentTestData + { + get + { + yield return new object[] + { + null, + null, + null + }; + + yield return new object[] + { + "Content_1", + "text/asp", + Encoding.ASCII + }; + } + } + + private class TestabilityController : Controller + { + public IActionResult View_Action(string viewName, object data) + { + return View(viewName, data); + } + + public IActionResult PartialView_Action(string viewName, object data) + { + return PartialView(viewName, data); + } + + public IActionResult Content_Action(string content, string contentType, Encoding encoding) + { + return Content(content, contentType, encoding); + } + + public IActionResult Created_Action(string uri, object data) + { + return Created(uri, data); + } + + public IActionResult FileContent_Action(string content, string contentType, string fileName) + { + var contentArray = string.IsNullOrEmpty(content) ? null : Encoding.UTF8.GetBytes(content); + return File(contentArray, contentType, fileName); + } + + public IActionResult FileStream_Action(string content, string contentType, string fileName) + { + var memoryStream = string.IsNullOrEmpty(content) ? null : + new MemoryStream(Encoding.UTF8.GetBytes(content)); + return File(memoryStream, contentType, fileName); + } + + public IActionResult Json_Action(object data) + { + return Json(data); + } + + public IActionResult Redirect_Action(string url) + { + return Redirect(url); + } + + public IActionResult RedirectToAction_Action(string actionName, string controllerName, object routeValues) + { + return RedirectToAction(actionName, controllerName, routeValues); + } + + public IActionResult RedirectToRoute_Action(string routeName, object routeValues) + { + return RedirectToRoute(routeName, routeValues); + } + + public IActionResult CreatedAtAction_Action(string actionName, string controllerName, object routeValues, object value) + { + return CreatedAtAction(actionName, controllerName, routeValues, value); + } + + public IActionResult CreatedAtRoute_Action(string routeName, object routeValues, object value) + { + return CreatedAtRoute(routeName, routeValues, value); + } + + public IActionResult HttpBadRequest_Action() + { + return HttpBadRequest(); + } + + public IActionResult HttpBadRequestObject_Action(object error) + { + return HttpBadRequest(error); + } + + public IActionResult HttpNotFound_Action() + { + return HttpNotFound(); + } + } + + private class MyModel + { + public string Property1 { get; set; } + public string Property2 { get; set; } + } + } +}