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