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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
// 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 Microsoft.AspNet.Mvc.ModelBinding;
using Xunit;
namespace Microsoft.AspNet.Mvc.Rendering
@ -12,8 +13,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
{
// 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 originalViewData = context.ViewData = new ViewDataDictionary(metadataProvider: null);
var replacementViewData = new ViewDataDictionary(metadataProvider: null);
var originalViewData = context.ViewData = new ViewDataDictionary(metadataProvider: new EmptyModelMetadataProvider());
var replacementViewData = new ViewDataDictionary(metadataProvider: new EmptyModelMetadataProvider());
// Act
context.ViewBag.Hello = "goodbye";

View File

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

View File

@ -26,7 +26,7 @@ namespace Microsoft.AspNet.Mvc.Core
Assert.NotNull(viewData.ModelState);
Assert.NotNull(viewData.TemplateInfo);
Assert.Null(viewData.Model);
Assert.Null(viewData.ModelMetadata);
Assert.NotNull(viewData.ModelMetadata);
Assert.Equal(0, viewData.Count);
}
@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Mvc.Core
Assert.Same(modelState, viewData.ModelState);
Assert.NotNull(viewData.TemplateInfo);
Assert.Null(viewData.Model);
Assert.Null(viewData.ModelMetadata);
Assert.NotNull(viewData.ModelMetadata);
Assert.Equal(0, viewData.Count);
}
@ -53,9 +53,14 @@ namespace Microsoft.AspNet.Mvc.Core
{
// Arrange
var metadataProvider = new Mock<IModelMetadataProvider>();
metadataProvider.Setup(m => m.GetMetadataForType(It.IsAny<Func<object>>(), typeof(TestModel)))
.Returns(new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(TestModel)))
.Verifiable();
metadataProvider
.Setup(m => m.GetMetadataForType(It.IsAny<Func<object>>(), typeof(object)))
.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 viewData = new TestViewDataDictionary(metadataProvider.Object, modelState);
var model = new TestModel();
@ -134,7 +139,7 @@ namespace Microsoft.AspNet.Mvc.Core
Assert.NotNull(viewData.ModelState);
Assert.NotNull(viewData.TemplateInfo);
Assert.Null(viewData.Model);
Assert.Null(viewData.ModelMetadata);
Assert.NotNull(viewData.ModelMetadata);
Assert.Equal("value1", viewData["key1"]);
Assert.IsType<CopyOnWriteDictionary<string, object>>(viewData.Data);
}
@ -148,45 +153,56 @@ namespace Microsoft.AspNet.Mvc.Core
source["key1"] = "value1";
// Act
var viewData = new ViewDataDictionary<int>(source, null);
var viewData = new ViewDataDictionary<int>(source, model: null);
// Assert
Assert.NotNull(viewData.ModelState);
Assert.NotNull(viewData.TemplateInfo);
Assert.Throws<NullReferenceException>(() => viewData.Model);
Assert.Equal(0, viewData.Model);
Assert.NotNull(viewData.ModelMetadata);
Assert.Equal("value1", viewData["key1"]);
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]
[InlineData(typeof(int))]
[InlineData(typeof(string))]
[InlineData(typeof(IEnumerable<string>))]
[InlineData(typeof(List<string>))]
[InlineData(typeof(string[]))]
[InlineData(typeof(Dictionary<string, object>))]
public void CopyConstructors_CopyModelMetadata(Type type)
[MemberData(nameof(CopyModelMetadataData))]
public void CopyConstructors_CopyModelMetadata(Type type, object instance)
{
// Arrange
var metadataProvider = new EmptyModelMetadataProvider();
var metadata = metadataProvider.GetMetadataForType(() => null, type);
var source = new ViewDataDictionary(metadataProvider)
{
ModelMetadata = metadata,
Model = instance,
};
// Act
var viewData1 = new ViewDataDictionary(source);
var viewData2 = new ViewDataDictionary(source, model: null);
var viewData2 = new ViewDataDictionary(source, model: instance);
// Assert
Assert.Same(metadata, viewData1.ModelMetadata);
Assert.Same(metadata, viewData2.ModelMetadata);
Assert.Same(source.ModelMetadata, viewData1.ModelMetadata);
Assert.Same(source.ModelMetadata, viewData2.ModelMetadata);
}
[Fact]
public void CopyConstructors_IgnoreModelMetadata_IfForTypeObject()
public void CopyConstructors_CopyModelMetadata_ForTypeObject()
{
// Arrange
var metadataProvider = new EmptyModelMetadataProvider();
@ -201,8 +217,8 @@ namespace Microsoft.AspNet.Mvc.Core
var viewData2 = new ViewDataDictionary(source, model: null);
// Assert
Assert.Null(viewData1.ModelMetadata);
Assert.Null(viewData2.ModelMetadata);
Assert.Same(metadata, viewData1.ModelMetadata);
Assert.Same(metadata, viewData2.ModelMetadata);
}
[Theory]
@ -240,6 +256,28 @@ namespace Microsoft.AspNet.Mvc.Core
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
{
get

View File

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

View File

@ -88,7 +88,7 @@ namespace Microsoft.AspNet.Mvc.Razor
.Returns(serviceProvider.Object);
var routeContext = new RouteContext(httpContext.Object);
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,
Mock.Of<IView>(),
viewData,

View File

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