aspnetcore/test/Microsoft.AspNet.Mvc.Razor..../RazorViewTest.cs

768 lines
28 KiB
C#

// 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.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.PageExecutionInstrumentation;
using Microsoft.AspNet.PipelineCore;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.Razor
{
public class RazorViewTest
{
private const string LayoutPath = "~/Shared/_Layout.cshtml";
private readonly RenderAsyncDelegate _nullRenderAsyncDelegate = async writer => { };
[Fact]
public async Task RenderAsync_ThrowsIfContextualizeHasNotBeenInvoked()
{
// Arrange
var page = new TestableRazorPage(v => { });
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
var viewContext = CreateViewContext(view);
var expected = viewContext.Writer;
// Act and Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
Assert.Equal("The 'Contextualize' method must be called before 'RenderAsync' can be invoked.",
ex.Message);
}
[Fact]
public async Task RenderAsync_AsPartial_DoesNotBufferOutput()
{
// Arrange
TextWriter actual = null;
var page = new TestableRazorPage(v =>
{
actual = v.Output;
v.Write("Hello world");
});
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: true);
var viewContext = CreateViewContext(view);
var expected = viewContext.Writer;
// Act
await view.RenderAsync(viewContext);
// Assert
Assert.Same(expected, actual);
Assert.Equal("Hello world", viewContext.Writer.ToString());
}
[Fact]
public async Task RenderAsync_AsPartial_ActivatesViews_WithThePassedInViewContext()
{
// Arrange
var viewData = new ViewDataDictionary(Mock.Of<IModelMetadataProvider>());
var page = new TestableRazorPage(v =>
{
// viewData is assigned to ViewContext by the activator
Assert.Same(viewData, v.ViewContext.ViewData);
});
var activator = new Mock<IRazorPageActivator>();
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
activator.Object,
CreateViewStartProvider());
view.Contextualize(page, isPartial: true);
var viewContext = CreateViewContext(view);
var expectedWriter = viewContext.Writer;
activator.Setup(a => a.Activate(page, It.IsAny<ViewContext>()))
.Callback((IRazorPage p, ViewContext c) =>
{
Assert.Same(c, viewContext);
c.ViewData = viewData;
})
.Verifiable();
// Act
await view.RenderAsync(viewContext);
// Assert
activator.Verify();
Assert.Same(expectedWriter, viewContext.Writer);
}
[Fact]
public async Task RenderAsync_AsPartial_ActivatesViews()
{
// Arrange
var page = new TestableRazorPage(v => { });
var activator = new Mock<IRazorPageActivator>();
activator.Setup(a => a.Activate(page, It.IsAny<ViewContext>()))
.Verifiable();
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
activator.Object,
CreateViewStartProvider());
view.Contextualize(page, isPartial: true);
var viewContext = CreateViewContext(view);
// Act
await view.RenderAsync(viewContext);
// Assert
activator.Verify();
}
[Fact]
public async Task RenderAsync_AsPartial_DoesNotExecuteLayoutOrViewStartPages()
{
var page = new TestableRazorPage(v =>
{
v.Layout = LayoutPath;
});
var pageFactory = new Mock<IRazorPageFactory>();
var viewStartProvider = CreateViewStartProvider();
var view = new RazorView(pageFactory.Object,
Mock.Of<IRazorPageActivator>(),
viewStartProvider);
view.Contextualize(page, isPartial: true);
var viewContext = CreateViewContext(view);
// Act
await view.RenderAsync(viewContext);
// Assert
pageFactory.Verify(v => v.CreateInstance(It.IsAny<string>()), Times.Never());
Mock.Get(viewStartProvider)
.Verify(v => v.GetViewStartPages(It.IsAny<string>()), Times.Never());
}
[Fact]
public async Task RenderAsync_CreatesOutputBuffer()
{
// Arrange
TextWriter actual = null;
var page = new TestableRazorPage(v =>
{
actual = v.Output;
});
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
var original = viewContext.Writer;
// Act
await view.RenderAsync(viewContext);
// Assert
Assert.IsType<RazorTextWriter>(actual);
Assert.NotSame(original, actual);
}
[Fact]
public async Task RenderAsync_CopiesBufferedContentToOutput()
{
// Arrange
var page = new TestableRazorPage(v =>
{
v.WriteLiteral("Hello world");
});
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
var original = viewContext.Writer;
// Act
await view.RenderAsync(viewContext);
// Assert
Assert.Equal("Hello world", original.ToString());
}
[Fact]
public async Task RenderAsync_ActivatesPages()
{
// Arrange
var page = new TestableRazorPage(v =>
{
v.WriteLiteral("Hello world");
});
var activator = new Mock<IRazorPageActivator>();
activator.Setup(a => a.Activate(page, It.IsAny<ViewContext>()))
.Verifiable();
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
activator.Object,
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
// Act
await view.RenderAsync(viewContext);
// Assert
activator.Verify();
}
[Fact]
public async Task RenderAsync_ExecutesViewStart()
{
// Arrange
var actualLayoutPath = "";
var layoutPath = "/Views/_Shared/_Layout.cshtml";
var viewStart1 = new TestableRazorPage(v =>
{
v.Layout = "/fake-layout-path";
});
var viewStart2 = new TestableRazorPage(v =>
{
v.Layout = layoutPath;
});
var page = new TestableRazorPage(v =>
{
// This path must have been set as a consequence of running viewStart
actualLayoutPath = v.Layout;
// Clear out layout so we don't render it
v.Layout = null;
});
var activator = new Mock<IRazorPageActivator>();
activator.Setup(a => a.Activate(viewStart1, It.IsAny<ViewContext>()))
.Verifiable();
activator.Setup(a => a.Activate(viewStart2, It.IsAny<ViewContext>()))
.Verifiable();
activator.Setup(a => a.Activate(page, It.IsAny<ViewContext>()))
.Verifiable();
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
activator.Object,
CreateViewStartProvider(viewStart1, viewStart2));
view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
// Act
await view.RenderAsync(viewContext);
// Assert
activator.Verify();
}
[Fact]
public async Task RenderAsync_ExecutesLayoutPages()
{
// Arrange
var expected =
@"layout-content
head-content
body-content
foot-content";
var page = new TestableRazorPage(v =>
{
v.WriteLiteral("body-content");
v.Layout = LayoutPath;
v.DefineSection("head", async writer =>
{
await writer.WriteAsync("head-content");
});
v.DefineSection("foot", async writer =>
{
await writer.WriteAsync("foot-content");
});
});
var layout = new TestableRazorPage(v =>
{
v.Write("layout-content" + Environment.NewLine);
v.Write(v.RenderSection("head"));
v.Write(Environment.NewLine);
v.RenderBodyPublic();
v.Write(Environment.NewLine);
v.Write(v.RenderSection("foot"));
});
var activator = new Mock<IRazorPageActivator>();
activator.Setup(a => a.Activate(page, It.IsAny<ViewContext>()))
.Verifiable();
activator.Setup(a => a.Activate(layout, It.IsAny<ViewContext>()))
.Verifiable();
var pageFactory = new Mock<IRazorPageFactory>();
pageFactory.Setup(p => p.CreateInstance(LayoutPath))
.Returns(layout);
var view = new RazorView(pageFactory.Object,
activator.Object,
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
// Act
await view.RenderAsync(viewContext);
// Assert
// Verify the activator was invoked for the primary page and layout page.
activator.Verify();
Assert.Equal(expected, viewContext.Writer.ToString());
}
[Fact]
public async Task RenderAsync_ThrowsIfSectionsWereDefinedButNotRendered()
{
// Arrange
var page = new TestableRazorPage(v =>
{
v.DefineSection("head", _nullRenderAsyncDelegate);
v.Layout = LayoutPath;
v.DefineSection("foot", _nullRenderAsyncDelegate);
});
var layout = new TestableRazorPage(v =>
{
v.RenderBodyPublic();
});
var pageFactory = new Mock<IRazorPageFactory>();
pageFactory.Setup(p => p.CreateInstance(LayoutPath))
.Returns(layout);
var view = new RazorView(pageFactory.Object,
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
// Act and Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
Assert.Equal("The following sections have been defined but have not been rendered: 'head, foot'.", ex.Message);
}
[Fact]
public async Task RenderAsync_ThrowsIfBodyWasNotRendered()
{
// Arrange
var page = new TestableRazorPage(v =>
{
v.Layout = LayoutPath;
});
var layout = new TestableRazorPage(v =>
{
});
var pageFactory = new Mock<IRazorPageFactory>();
pageFactory.Setup(p => p.CreateInstance(LayoutPath))
.Returns(layout);
var view = new RazorView(pageFactory.Object,
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
// Act and Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
Assert.Equal("RenderBody must be called from a layout page.", ex.Message);
}
[Fact]
public async Task RenderAsync_ExecutesNestedLayoutPages()
{
// Arrange
var expected =
@"layout-2
bar-content
layout-1
foo-content
body-content";
var page = new TestableRazorPage(v =>
{
v.DefineSection("foo", async writer =>
{
await writer.WriteLineAsync("foo-content");
});
v.Layout = "~/Shared/Layout1.cshtml";
v.WriteLiteral("body-content");
});
var layout1 = new TestableRazorPage(v =>
{
v.Write("layout-1" + Environment.NewLine);
v.Write(v.RenderSection("foo"));
v.DefineSection("bar", writer => writer.WriteLineAsync("bar-content"));
v.RenderBodyPublic();
v.Layout = "~/Shared/Layout2.cshtml";
});
var layout2 = new TestableRazorPage(v =>
{
v.Write("layout-2" + Environment.NewLine);
v.Write(v.RenderSection("bar"));
v.RenderBodyPublic();
});
var pageFactory = new Mock<IRazorPageFactory>();
pageFactory.Setup(p => p.CreateInstance("~/Shared/Layout1.cshtml"))
.Returns(layout1);
pageFactory.Setup(p => p.CreateInstance("~/Shared/Layout2.cshtml"))
.Returns(layout2);
var view = new RazorView(pageFactory.Object,
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
// Act
await view.RenderAsync(viewContext);
// Assert
Assert.Equal(expected, viewContext.Writer.ToString());
}
[Fact]
public async Task RenderAsync_DoesNotCopyContentOnceRazorTextWriterIsNoLongerBuffering()
{
// Arrange
var expected =
@"layout-1
body content
section-content-1
section-content-2";
var page = new TestableRazorPage(v =>
{
v.Layout = "layout-1";
v.WriteLiteral("body content" + Environment.NewLine);
v.DefineSection("foo", async _ =>
{
v.WriteLiteral("section-content-1" + Environment.NewLine);
await v.FlushAsync();
v.WriteLiteral("section-content-2");
});
});
var layout1 = new TestableRazorPage(v =>
{
v.Write("layout-1" + Environment.NewLine);
v.RenderBodyPublic();
v.Write(v.RenderSection("foo"));
});
var pageFactory = new Mock<IRazorPageFactory>();
pageFactory.Setup(p => p.CreateInstance("layout-1"))
.Returns(layout1);
var view = new RazorView(pageFactory.Object,
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
// Act
await view.RenderAsync(viewContext);
// Assert
Assert.Equal(expected, viewContext.Writer.ToString());
}
[Fact]
public async Task FlushAsync_DoesNotThrowWhenInvokedInsideOfASection()
{
// Arrange
var expected =
@"layout-1
section-content-1
section-content-2";
var page = new TestableRazorPage(v =>
{
v.Layout = "layout-1";
v.DefineSection("foo", async _ =>
{
v.WriteLiteral("section-content-1" + Environment.NewLine);
await v.FlushAsync();
v.WriteLiteral("section-content-2");
});
});
var layout1 = new TestableRazorPage(v =>
{
v.Write("layout-1" + Environment.NewLine);
v.RenderBodyPublic();
v.Write(v.RenderSection("foo"));
});
var pageFactory = new Mock<IRazorPageFactory>();
pageFactory.Setup(p => p.CreateInstance("layout-1"))
.Returns(layout1);
var view = new RazorView(pageFactory.Object,
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
// Act
await view.RenderAsync(viewContext);
// Assert
Assert.Equal(expected, viewContext.Writer.ToString());
}
[Fact]
public async Task RenderAsync_ThrowsIfLayoutIsSpecifiedWhenNotBuffered()
{
// Arrange
var expected = @"A layout page cannot be rendered after 'FlushAsync' has been invoked.";
var page = new TestableRazorPage(v =>
{
v.WriteLiteral("before-flush" + Environment.NewLine);
v.FlushAsync().Wait();
v.Layout = "test-layout";
v.WriteLiteral("after-flush");
});
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
// Act and Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
Assert.Equal(expected, ex.Message);
}
[Fact]
public async Task RenderAsync_ThrowsIfFlushWasInvokedInsideRenderedSectionAndLayoutWasSet()
{
// Arrange
var expected = @"A layout page cannot be rendered after 'FlushAsync' has been invoked.";
var page = new TestableRazorPage(v =>
{
v.DefineSection("foo", async writer =>
{
writer.WriteLine("foo-content");
await v.FlushAsync();
});
v.Layout = "~/Shared/Layout1.cshtml";
v.WriteLiteral("body-content");
});
var layout1 = new TestableRazorPage(v =>
{
v.Write("layout-1" + Environment.NewLine);
v.Write(v.RenderSection("foo"));
v.DefineSection("bar", writer => writer.WriteLineAsync("bar-content"));
v.RenderBodyPublic();
v.Layout = "~/Shared/Layout2.cshtml";
});
var pageFactory = new Mock<IRazorPageFactory>();
pageFactory.Setup(p => p.CreateInstance("~/Shared/Layout1.cshtml"))
.Returns(layout1);
var view = new RazorView(pageFactory.Object,
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
// Act and Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
Assert.Equal(expected, ex.Message);
}
[Fact]
public async Task RenderAsync_UsesPageExecutionFeatureFromRequest_ToWrapWriter()
{
// Arrange
var pageWriter = CreateBufferedWriter();
var layoutWriter = CreateBufferedWriter();
var layoutExecuted = false;
var count = -1;
var feature = new Mock<IPageExecutionListenerFeature>(MockBehavior.Strict);
feature.Setup(f => f.DecorateWriter(It.IsAny<RazorTextWriter>()))
.Returns(() =>
{
count++;
if (count == 0)
{
return pageWriter;
}
else if (count == 1)
{
return layoutWriter;
}
throw new Exception();
})
.Verifiable();
var pageContext = Mock.Of<IPageExecutionContext>();
feature.Setup(f => f.GetContext("/MyPage.cshtml", pageWriter))
.Returns(pageContext)
.Verifiable();
var layoutContext = Mock.Of<IPageExecutionContext>();
feature.Setup(f => f.GetContext("/Layout.cshtml", layoutWriter))
.Returns(layoutContext)
.Verifiable();
var page = new TestableRazorPage(v =>
{
v.Layout = "/Layout.cshtml";
Assert.Same(pageWriter, v.Output);
Assert.Same(pageContext, v.PageExecutionContext);
});
page.Path = "/MyPage.cshtml";
var layout = new TestableRazorPage(v =>
{
Assert.Same(layoutWriter, v.Output);
Assert.Same(layoutContext, v.PageExecutionContext);
v.RenderBodyPublic();
layoutExecuted = true;
});
layout.Path = "/Layout.cshtml";
var pageFactory = new Mock<IRazorPageFactory>();
pageFactory.Setup(p => p.CreateInstance("/Layout.cshtml"))
.Returns(layout);
var viewStartProvider = new Mock<IViewStartProvider>();
viewStartProvider.Setup(v => v.GetViewStartPages(It.IsAny<string>()))
.Returns(Enumerable.Empty<IRazorPage>())
.Verifiable();
var view = new RazorView(pageFactory.Object,
Mock.Of<IRazorPageActivator>(),
viewStartProvider.Object);
view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
viewContext.HttpContext.SetFeature<IPageExecutionListenerFeature>(feature.Object);
// Act
await view.RenderAsync(viewContext);
// Assert
feature.Verify();
viewStartProvider.Verify();
Assert.True(layoutExecuted);
}
[Fact]
public async Task RenderAsync_UsesPageExecutionFeatureFromRequest_ToGetExecutionContext()
{
// Arrange
var writer = new StringWriter();
var executed = false;
var feature = new Mock<IPageExecutionListenerFeature>(MockBehavior.Strict);
var pageContext = Mock.Of<IPageExecutionContext>();
feature.Setup(f => f.GetContext("/MyPartialPage.cshtml", It.IsAny<RazorTextWriter>()))
.Returns(pageContext)
.Verifiable();
feature.Setup(f => f.DecorateWriter(It.IsAny<RazorTextWriter>()))
.Returns((RazorTextWriter r) => r)
.Verifiable();
var page = new TestableRazorPage(v =>
{
Assert.IsType<RazorTextWriter>(v.Output);
Assert.Same(pageContext, v.PageExecutionContext);
executed = true;
v.Write("Hello world");
});
page.Path = "/MyPartialPage.cshtml";
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
Mock.Of<IRazorPageActivator>(),
Mock.Of<IViewStartProvider>());
view.Contextualize(page, isPartial: true);
var viewContext = CreateViewContext(view);
viewContext.Writer = writer;
viewContext.HttpContext.SetFeature<IPageExecutionListenerFeature>(feature.Object);
// Act
await view.RenderAsync(viewContext);
// Assert
feature.Verify();
Assert.True(executed);
Assert.Equal("Hello world", viewContext.Writer.ToString());
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task RenderAsync_DoesNotSetExecutionContextWhenListenerIsNotRegistered(bool isPartial)
{
// Arrange
var executed = false;
var page = new TestableRazorPage(v =>
{
Assert.Null(v.PageExecutionContext);
executed = true;
});
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
Mock.Of<IRazorPageActivator>(),
Mock.Of<IViewStartProvider>());
view.Contextualize(page, isPartial);
var viewContext = CreateViewContext(view);
// Act
await view.RenderAsync(viewContext);
// Assert
Assert.True(executed);
}
private static TextWriter CreateBufferedWriter()
{
var mockWriter = new Mock<TextWriter>();
var bufferedWriter = mockWriter.As<IBufferedTextWriter>();
bufferedWriter.SetupGet(b => b.IsBuffering)
.Returns(true);
return mockWriter.Object;
}
private static ViewContext CreateViewContext(RazorView view)
{
var httpContext = new DefaultHttpContext();
var actionContext = new ActionContext(httpContext, routeData: null, actionDescriptor: null);
return new ViewContext(
actionContext,
view,
new ViewDataDictionary(Mock.Of<IModelMetadataProvider>()),
new StringWriter());
}
private static IViewStartProvider CreateViewStartProvider(params IRazorPage[] viewStartPages)
{
viewStartPages = viewStartPages ?? new IRazorPage[0];
var viewStartProvider = new Mock<IViewStartProvider>();
viewStartProvider.Setup(v => v.GetViewStartPages(It.IsAny<string>()))
.Returns(viewStartPages);
return viewStartProvider.Object;
}
private class TestableRazorPage : RazorPage
{
private readonly Action<TestableRazorPage> _executeAction;
public TestableRazorPage(Action<TestableRazorPage> executeAction)
{
_executeAction = executeAction;
}
public void RenderBodyPublic()
{
Write(RenderBody());
}
public override Task ExecuteAsync()
{
_executeAction(this);
return Task.FromResult(0);
}
}
}
}