Adjust tests to handle new `ViewDataDictionary` invariants

- ensure `ViewDataDictionary` constructors are not passed a `null` or
  `Mock.Of<IModelMetadataProvider>()` instance
 - `ViewDataDictionary` constructors always use the `IModelMetadataProvider`
- `viewData.ModelMetadata` now never `null`
- `ViewDataDictionary<int>.Model` no longer throws if read before it's written
- `ViewDataDictionary.ModelMetadata` now copied to new instances in fewer cases
 - e.g. don't use unusual `object` datatype with customized `ModelMetadata`
This commit is contained in:
Doug Bunting 2014-10-25 15:36:19 -07:00
parent 97aaa7049e
commit c89bca5924
11 changed files with 83 additions and 44 deletions

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
services.Setup(s => s.GetService(typeof(IUrlHelper))) services.Setup(s => s.GetService(typeof(IUrlHelper)))
.Returns(Mock.Of<IUrlHelper>()); .Returns(Mock.Of<IUrlHelper>());
services.Setup(s => s.GetService(typeof(IModelMetadataProvider))) services.Setup(s => s.GetService(typeof(IModelMetadataProvider)))
.Returns(Mock.Of<IModelMetadataProvider>()); .Returns(new EmptyModelMetadataProvider());
var httpRequest = Mock.Of<HttpRequest>(); var httpRequest = Mock.Of<HttpRequest>();
var httpContext = new Mock<HttpContext>(); var httpContext = new Mock<HttpContext>();
@ -53,7 +53,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
// Arrange // Arrange
var services = new Mock<IServiceProvider>(); var services = new Mock<IServiceProvider>();
services.Setup(s => s.GetService(typeof(IModelMetadataProvider))) services.Setup(s => s.GetService(typeof(IModelMetadataProvider)))
.Returns(Mock.Of<IModelMetadataProvider>()); .Returns(new EmptyModelMetadataProvider());
services.Setup(s => s.GetService(typeof(ICompositeViewEngine))) services.Setup(s => s.GetService(typeof(ICompositeViewEngine)))
.Returns(Mock.Of<ICompositeViewEngine>()); .Returns(Mock.Of<ICompositeViewEngine>());
services.Setup(s => s.GetService(typeof(IUrlHelper))) services.Setup(s => s.GetService(typeof(IUrlHelper)))
@ -86,7 +86,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
services.Setup(s => s.GetService(typeof(IUrlHelper))) services.Setup(s => s.GetService(typeof(IUrlHelper)))
.Returns(urlHelper); .Returns(urlHelper);
services.Setup(s => s.GetService(typeof(IModelMetadataProvider))) services.Setup(s => s.GetService(typeof(IModelMetadataProvider)))
.Returns(Mock.Of<IModelMetadataProvider>()); .Returns(new EmptyModelMetadataProvider());
var httpContext = new Mock<HttpContext>(); var httpContext = new Mock<HttpContext>();
httpContext.SetupGet(c => c.RequestServices) httpContext.SetupGet(c => c.RequestServices)
@ -114,7 +114,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
services.Setup(s => s.GetService(typeof(IUrlHelper))) services.Setup(s => s.GetService(typeof(IUrlHelper)))
.Returns(Mock.Of<IUrlHelper>()); .Returns(Mock.Of<IUrlHelper>());
services.Setup(s => s.GetService(typeof(IModelMetadataProvider))) services.Setup(s => s.GetService(typeof(IModelMetadataProvider)))
.Returns(Mock.Of<IModelMetadataProvider>()); .Returns(new EmptyModelMetadataProvider());
var httpContext = new Mock<HttpContext>(); var httpContext = new Mock<HttpContext>();
httpContext.SetupGet(c => c.Response) httpContext.SetupGet(c => c.Response)

View File

@ -105,7 +105,7 @@ namespace Microsoft.AspNet.Mvc
var actionContext = new ActionContext(routeContext, new ActionDescriptor()); var actionContext = new ActionContext(routeContext, new ActionDescriptor());
return new ViewContext(actionContext, return new ViewContext(actionContext,
Mock.Of<IView>(), Mock.Of<IView>(),
new ViewDataDictionary(Mock.Of<IModelMetadataProvider>()), new ViewDataDictionary(new EmptyModelMetadataProvider()),
TextWriter.Null); TextWriter.Null);
} }

View File

@ -86,7 +86,7 @@ namespace Microsoft.AspNet.Mvc.Core
new DataAnnotationsModelMetadataProvider(), new DataAnnotationsModelMetadataProvider(),
containerType: null, containerType: null,
modelAccessor: null, modelAccessor: null,
modelType: typeof(object), modelType: typeof(string), // Ensure FromStringExpression() doesn't ignore the ModelMetadata.
propertyName: propertyName); propertyName: propertyName);
var helper = DefaultTemplatesUtilities.GetHtmlHelper(); var helper = DefaultTemplatesUtilities.GetHtmlHelper();

View File

@ -101,7 +101,7 @@ namespace Microsoft.AspNet.Mvc.Core
new DataAnnotationsModelMetadataProvider(), new DataAnnotationsModelMetadataProvider(),
containerType: null, containerType: null,
modelAccessor: null, modelAccessor: null,
modelType: typeof(object), modelType: typeof(string), // Ensure FromStringExpression() doesn't ignore the ModelMetadata.
propertyName: propertyName); propertyName: propertyName);
var helper = DefaultTemplatesUtilities.GetHtmlHelper(); var helper = DefaultTemplatesUtilities.GetHtmlHelper();

View File

@ -164,7 +164,7 @@ namespace Microsoft.AspNet.Mvc.Core
// Only the ViewDataDictionary should do anything with metadata. // Only the ViewDataDictionary should do anything with metadata.
provider.Verify( provider.Verify(
m => m.GetMetadataForType(It.IsAny<Func<object>>(), typeof(DefaultTemplatesUtilities.ObjectTemplateModel)), m => m.GetMetadataForType(It.IsAny<Func<object>>(), typeof(DefaultTemplatesUtilities.ObjectTemplateModel)),
Times.Once); Times.Exactly(2));
} }
[Fact] [Fact]
@ -191,7 +191,7 @@ namespace Microsoft.AspNet.Mvc.Core
// Only the ViewDataDictionary should do anything with metadata. // Only the ViewDataDictionary should do anything with metadata.
provider.Verify( provider.Verify(
m => m.GetMetadataForType(It.IsAny<Func<object>>(), typeof(DefaultTemplatesUtilities.ObjectTemplateModel)), m => m.GetMetadataForType(It.IsAny<Func<object>>(), typeof(DefaultTemplatesUtilities.ObjectTemplateModel)),
Times.Once); Times.Exactly(2));
} }
[Theory] [Theory]

View File

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc.ModelBinding;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Mvc.Rendering namespace Microsoft.AspNet.Mvc.Rendering
@ -12,8 +13,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
{ {
// Arrange (eventually passing null to these consturctors will throw) // Arrange (eventually passing null to these consturctors will throw)
var context = new ViewContext(new ActionContext(null, null, null), view: null, viewData: null, writer: null); var context = new ViewContext(new ActionContext(null, null, null), view: null, viewData: null, writer: null);
var originalViewData = context.ViewData = new ViewDataDictionary(metadataProvider: null); var originalViewData = context.ViewData = new ViewDataDictionary(metadataProvider: new EmptyModelMetadataProvider());
var replacementViewData = new ViewDataDictionary(metadataProvider: null); var replacementViewData = new ViewDataDictionary(metadataProvider: new EmptyModelMetadataProvider());
// Act // Act
context.ViewBag.Hello = "goodbye"; context.ViewBag.Hello = "goodbye";

View File

@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc
// Arrange // Arrange
var viewComponent = new TestViewComponent() var viewComponent = new TestViewComponent()
{ {
ViewData = new ViewDataDictionary(metadataProvider: null), ViewData = new ViewDataDictionary(metadataProvider: new EmptyModelMetadataProvider()),
}; };
// Act // Act
@ -35,7 +35,7 @@ namespace Microsoft.AspNet.Mvc
// Arrange // Arrange
var viewComponent = new TestViewComponent() var viewComponent = new TestViewComponent()
{ {
ViewData = new ViewDataDictionary(metadataProvider: null), ViewData = new ViewDataDictionary(metadataProvider: new EmptyModelMetadataProvider()),
}; };
// Act // Act

View File

@ -26,7 +26,7 @@ namespace Microsoft.AspNet.Mvc.Core
Assert.NotNull(viewData.ModelState); Assert.NotNull(viewData.ModelState);
Assert.NotNull(viewData.TemplateInfo); Assert.NotNull(viewData.TemplateInfo);
Assert.Null(viewData.Model); Assert.Null(viewData.Model);
Assert.Null(viewData.ModelMetadata); Assert.NotNull(viewData.ModelMetadata);
Assert.Equal(0, viewData.Count); Assert.Equal(0, viewData.Count);
} }
@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Mvc.Core
Assert.Same(modelState, viewData.ModelState); Assert.Same(modelState, viewData.ModelState);
Assert.NotNull(viewData.TemplateInfo); Assert.NotNull(viewData.TemplateInfo);
Assert.Null(viewData.Model); Assert.Null(viewData.Model);
Assert.Null(viewData.ModelMetadata); Assert.NotNull(viewData.ModelMetadata);
Assert.Equal(0, viewData.Count); Assert.Equal(0, viewData.Count);
} }
@ -53,9 +53,14 @@ namespace Microsoft.AspNet.Mvc.Core
{ {
// Arrange // Arrange
var metadataProvider = new Mock<IModelMetadataProvider>(); var metadataProvider = new Mock<IModelMetadataProvider>();
metadataProvider.Setup(m => m.GetMetadataForType(It.IsAny<Func<object>>(), typeof(TestModel))) metadataProvider
.Returns(new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(TestModel))) .Setup(m => m.GetMetadataForType(It.IsAny<Func<object>>(), typeof(object)))
.Verifiable(); .Returns(new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(object)))
.Verifiable();
metadataProvider
.Setup(m => m.GetMetadataForType(It.IsAny<Func<object>>(), typeof(TestModel)))
.Returns(new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(TestModel)))
.Verifiable();
var modelState = new ModelStateDictionary(); var modelState = new ModelStateDictionary();
var viewData = new TestViewDataDictionary(metadataProvider.Object, modelState); var viewData = new TestViewDataDictionary(metadataProvider.Object, modelState);
var model = new TestModel(); var model = new TestModel();
@ -134,7 +139,7 @@ namespace Microsoft.AspNet.Mvc.Core
Assert.NotNull(viewData.ModelState); Assert.NotNull(viewData.ModelState);
Assert.NotNull(viewData.TemplateInfo); Assert.NotNull(viewData.TemplateInfo);
Assert.Null(viewData.Model); Assert.Null(viewData.Model);
Assert.Null(viewData.ModelMetadata); Assert.NotNull(viewData.ModelMetadata);
Assert.Equal("value1", viewData["key1"]); Assert.Equal("value1", viewData["key1"]);
Assert.IsType<CopyOnWriteDictionary<string, object>>(viewData.Data); Assert.IsType<CopyOnWriteDictionary<string, object>>(viewData.Data);
} }
@ -148,45 +153,56 @@ namespace Microsoft.AspNet.Mvc.Core
source["key1"] = "value1"; source["key1"] = "value1";
// Act // Act
var viewData = new ViewDataDictionary<int>(source, null); var viewData = new ViewDataDictionary<int>(source, model: null);
// Assert // Assert
Assert.NotNull(viewData.ModelState); Assert.NotNull(viewData.ModelState);
Assert.NotNull(viewData.TemplateInfo); Assert.NotNull(viewData.TemplateInfo);
Assert.Throws<NullReferenceException>(() => viewData.Model); Assert.Equal(0, viewData.Model);
Assert.NotNull(viewData.ModelMetadata); Assert.NotNull(viewData.ModelMetadata);
Assert.Equal("value1", viewData["key1"]); Assert.Equal("value1", viewData["key1"]);
Assert.IsType<CopyOnWriteDictionary<string, object>>(viewData.Data); Assert.IsType<CopyOnWriteDictionary<string, object>>(viewData.Data);
} }
public static TheoryData<Type, object> CopyModelMetadataData
{
get
{
// Instances in this data set must have exactly the same type as the corresponding Type. Otherwise
// the copy constructor ignores the source ModelMetadata.
return new TheoryData<Type, object>
{
{ typeof(int), 23 },
{ typeof(string), "hello" },
{ typeof(List<string>), new List<string>() },
{ typeof(string[]), new string[0] },
{ typeof(Dictionary<string, object>), new Dictionary<string, object>() },
};
}
}
[Theory] [Theory]
[InlineData(typeof(int))] [MemberData(nameof(CopyModelMetadataData))]
[InlineData(typeof(string))] public void CopyConstructors_CopyModelMetadata(Type type, object instance)
[InlineData(typeof(IEnumerable<string>))]
[InlineData(typeof(List<string>))]
[InlineData(typeof(string[]))]
[InlineData(typeof(Dictionary<string, object>))]
public void CopyConstructors_CopyModelMetadata(Type type)
{ {
// Arrange // Arrange
var metadataProvider = new EmptyModelMetadataProvider(); var metadataProvider = new EmptyModelMetadataProvider();
var metadata = metadataProvider.GetMetadataForType(() => null, type);
var source = new ViewDataDictionary(metadataProvider) var source = new ViewDataDictionary(metadataProvider)
{ {
ModelMetadata = metadata, Model = instance,
}; };
// Act // Act
var viewData1 = new ViewDataDictionary(source); var viewData1 = new ViewDataDictionary(source);
var viewData2 = new ViewDataDictionary(source, model: null); var viewData2 = new ViewDataDictionary(source, model: instance);
// Assert // Assert
Assert.Same(metadata, viewData1.ModelMetadata); Assert.Same(source.ModelMetadata, viewData1.ModelMetadata);
Assert.Same(metadata, viewData2.ModelMetadata); Assert.Same(source.ModelMetadata, viewData2.ModelMetadata);
} }
[Fact] [Fact]
public void CopyConstructors_IgnoreModelMetadata_IfForTypeObject() public void CopyConstructors_CopyModelMetadata_ForTypeObject()
{ {
// Arrange // Arrange
var metadataProvider = new EmptyModelMetadataProvider(); var metadataProvider = new EmptyModelMetadataProvider();
@ -201,8 +217,8 @@ namespace Microsoft.AspNet.Mvc.Core
var viewData2 = new ViewDataDictionary(source, model: null); var viewData2 = new ViewDataDictionary(source, model: null);
// Assert // Assert
Assert.Null(viewData1.ModelMetadata); Assert.Same(metadata, viewData1.ModelMetadata);
Assert.Null(viewData2.ModelMetadata); Assert.Same(metadata, viewData2.ModelMetadata);
} }
[Theory] [Theory]
@ -240,6 +256,28 @@ namespace Microsoft.AspNet.Mvc.Core
Assert.Equal(expectedType, viewData2.ModelMetadata.RealModelType); Assert.Equal(expectedType, viewData2.ModelMetadata.RealModelType);
} }
[Fact]
public void CopyConstructors_OverrideSourceMetadata_IfDeclaredTypeChanged()
{
// Arrange
var expectedType = typeof(string);
var metadataProvider = new EmptyModelMetadataProvider();
var source = new ViewDataDictionary<int>(metadataProvider);
// Act
var viewData1 = new ViewDataDictionary<string>(source);
var viewData2 = new ViewDataDictionary<string>(source, model: null);
// Assert
Assert.NotNull(viewData1.ModelMetadata);
Assert.Equal(expectedType, viewData1.ModelMetadata.ModelType);
Assert.Equal(expectedType, viewData1.ModelMetadata.RealModelType);
Assert.NotNull(viewData2.ModelMetadata);
Assert.Equal(expectedType, viewData2.ModelMetadata.ModelType);
Assert.Equal(expectedType, viewData2.ModelMetadata.RealModelType);
}
public static TheoryData<object, string, object> Eval_EvaluatesExpressionsData public static TheoryData<object, string, object> Eval_EvaluatesExpressionsData
{ {
get get

View File

@ -38,7 +38,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var actionContext = new ActionContext(routeContext, new ActionDescriptor()); var actionContext = new ActionContext(routeContext, new ActionDescriptor());
var viewContext = new ViewContext(actionContext, var viewContext = new ViewContext(actionContext,
Mock.Of<IView>(), Mock.Of<IView>(),
new ViewDataDictionary(Mock.Of<IModelMetadataProvider>()), new ViewDataDictionary(new EmptyModelMetadataProvider()),
TextWriter.Null); TextWriter.Null);
// Act // Act
@ -68,7 +68,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var actionContext = new ActionContext(routeContext, new ActionDescriptor()); var actionContext = new ActionContext(routeContext, new ActionDescriptor());
var viewContext = new ViewContext(actionContext, var viewContext = new ViewContext(actionContext,
Mock.Of<IView>(), Mock.Of<IView>(),
new ViewDataDictionary(Mock.Of<IModelMetadataProvider>()), new ViewDataDictionary(new EmptyModelMetadataProvider()),
TextWriter.Null); TextWriter.Null);
// Act and Assert // Act and Assert
@ -101,7 +101,7 @@ namespace Microsoft.AspNet.Mvc.Razor
.Returns(serviceProvider.Object); .Returns(serviceProvider.Object);
var routeContext = new RouteContext(httpContext.Object); var routeContext = new RouteContext(httpContext.Object);
var actionContext = new ActionContext(routeContext, new ActionDescriptor()); var actionContext = new ActionContext(routeContext, new ActionDescriptor());
var viewData = new ViewDataDictionary(Mock.Of<IModelMetadataProvider>()) var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider())
{ {
Model = new MyModel() Model = new MyModel()
}; };
@ -136,7 +136,7 @@ namespace Microsoft.AspNet.Mvc.Razor
.Returns(serviceProvider.Object); .Returns(serviceProvider.Object);
var routeContext = new RouteContext(httpContext.Object); var routeContext = new RouteContext(httpContext.Object);
var actionContext = new ActionContext(routeContext, new ActionDescriptor()); var actionContext = new ActionContext(routeContext, new ActionDescriptor());
var viewData = new ViewDataDictionary<MyModel>(Mock.Of<IModelMetadataProvider>()) var viewData = new ViewDataDictionary<MyModel>(new EmptyModelMetadataProvider())
{ {
Model = new MyModel() Model = new MyModel()
}; };
@ -171,7 +171,7 @@ namespace Microsoft.AspNet.Mvc.Razor
.Returns(serviceProvider.Object); .Returns(serviceProvider.Object);
var routeContext = new RouteContext(httpContext.Object); var routeContext = new RouteContext(httpContext.Object);
var actionContext = new ActionContext(routeContext, new ActionDescriptor()); var actionContext = new ActionContext(routeContext, new ActionDescriptor());
var viewData = new ViewDataDictionary(Mock.Of<IModelMetadataProvider>()); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
var viewContext = new ViewContext(actionContext, var viewContext = new ViewContext(actionContext,
Mock.Of<IView>(), Mock.Of<IView>(),
viewData, viewData,

View File

@ -88,7 +88,7 @@ namespace Microsoft.AspNet.Mvc.Razor
.Returns(serviceProvider.Object); .Returns(serviceProvider.Object);
var routeContext = new RouteContext(httpContext.Object); var routeContext = new RouteContext(httpContext.Object);
var actionContext = new ActionContext(routeContext, new ActionDescriptor()); var actionContext = new ActionContext(routeContext, new ActionDescriptor());
var viewData = new ViewDataDictionary(Mock.Of<IModelMetadataProvider>()); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
var viewContext = new ViewContext(actionContext, var viewContext = new ViewContext(actionContext,
Mock.Of<IView>(), Mock.Of<IView>(),
viewData, viewData,

View File

@ -64,7 +64,7 @@ namespace Microsoft.AspNet.Mvc.Razor
public async Task RenderAsync_AsPartial_ActivatesViews_WithThePassedInViewContext() public async Task RenderAsync_AsPartial_ActivatesViews_WithThePassedInViewContext()
{ {
// Arrange // Arrange
var viewData = new ViewDataDictionary(Mock.Of<IModelMetadataProvider>()); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
var page = new TestableRazorPage(v => var page = new TestableRazorPage(v =>
{ {
// viewData is assigned to ViewContext by the activator // viewData is assigned to ViewContext by the activator
@ -867,7 +867,7 @@ section-content-2";
return new ViewContext( return new ViewContext(
actionContext, actionContext,
view, view,
new ViewDataDictionary(Mock.Of<IModelMetadataProvider>()), new ViewDataDictionary(new EmptyModelMetadataProvider()),
new StringWriter()); new StringWriter());
} }