// Copyright (c) .NET Foundation. 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.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewComponents; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.WebEncoders.Testing; using Moq; using Xunit; namespace Microsoft.AspNetCore.Mvc { public class ViewViewComponentResultTest { private readonly ITempDataDictionary _tempDataDictionary = new TempDataDictionary(new DefaultHttpContext(), new SessionStateTempDataProvider()); [Fact] public void Execute_RendersPartialViews() { // Arrange var view = new Mock(); view.Setup(v => v.RenderAsync(It.IsAny())) .Returns(Task.FromResult(result: true)) .Verifiable(); var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.GetView(/*executingFilePath*/ null, "some-view", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound("some-view", Enumerable.Empty())) .Verifiable(); viewEngine .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/some-view", /*isMainPage*/ false)) .Returns(ViewEngineResult.Found("Components/Invoke/some-view", view.Object)) .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); var result = new ViewViewComponentResult { ViewEngine = viewEngine.Object, ViewName = "some-view", ViewData = viewData, TempData = _tempDataDictionary, }; var viewComponentContext = GetViewComponentContext(view.Object, viewData); // Act result.Execute(viewComponentContext); // Assert viewEngine.Verify(); view.Verify(); } [Fact] public void Execute_ResolvesView_WithDefaultAsViewName() { // Arrange var view = new Mock(MockBehavior.Strict); view.Setup(v => v.RenderAsync(It.IsAny())) .Returns(Task.FromResult(result: true)) .Verifiable(); var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/Default", /*isMainPage*/ false)) .Returns(ViewEngineResult.Found("Components/Invoke/Default", view.Object)) .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); var result = new ViewViewComponentResult { ViewEngine = viewEngine.Object, ViewData = viewData, TempData = _tempDataDictionary, }; var viewComponentContext = GetViewComponentContext(view.Object, viewData); // Act result.Execute(viewComponentContext); // Assert viewEngine.Verify(); view.Verify(); } [Fact] public void Execute_ResolvesView_AndWritesDiagnosticSource() { // Arrange var view = new Mock(MockBehavior.Strict); view.Setup(v => v.RenderAsync(It.IsAny())) .Returns(Task.FromResult(result: true)) .Verifiable(); var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/Default", /*isMainPage*/ false)) .Returns(ViewEngineResult.Found("Components/Invoke/Default", view.Object)) .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); var result = new ViewViewComponentResult { ViewEngine = viewEngine.Object, ViewData = viewData, TempData = _tempDataDictionary, }; var adapter = new TestDiagnosticListener(); var viewComponentContext = GetViewComponentContext(view.Object, viewData, adapter); // Act result.Execute(viewComponentContext); // Assert viewEngine.Verify(); view.Verify(); Assert.NotNull(adapter.ViewComponentBeforeViewExecute?.ActionDescriptor); Assert.NotNull(adapter.ViewComponentBeforeViewExecute?.ViewComponentContext); Assert.NotNull(adapter.ViewComponentBeforeViewExecute?.View); Assert.NotNull(adapter.ViewComponentAfterViewExecute?.ActionDescriptor); Assert.NotNull(adapter.ViewComponentAfterViewExecute?.ViewComponentContext); Assert.NotNull(adapter.ViewComponentAfterViewExecute?.View); } [Fact] public void Execute_ThrowsIfPartialViewCannotBeFound_MessageUsesGetViewLocations() { // Arrange var expected = string.Join(Environment.NewLine, "The view 'Components/Invoke/some-view' was not found. The following locations were searched:", "location1", "location2"); var view = Mock.Of(); var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.GetView(/*executingFilePath*/ null, "some-view", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound("some-view", new[] { "location1", "location2" })) .Verifiable(); viewEngine .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/some-view", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound("Components/Invoke/some-view", Enumerable.Empty())) .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); var result = new ViewViewComponentResult { ViewEngine = viewEngine.Object, ViewName = "some-view", ViewData = viewData, TempData = _tempDataDictionary, }; var viewComponentContext = GetViewComponentContext(view, viewData); // Act and Assert var ex = Assert.Throws(() => result.Execute(viewComponentContext)); Assert.Equal(expected, ex.Message); } [Fact] public void Execute_ThrowsIfPartialViewCannotBeFound_MessageUsesFindViewLocations() { // Arrange var expected = string.Join(Environment.NewLine, "The view 'Components/Invoke/some-view' was not found. The following locations were searched:", "location1", "location2"); var view = Mock.Of(); var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.GetView(/*executingFilePath*/ null, "some-view", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound("some-view", Enumerable.Empty())) .Verifiable(); viewEngine .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/some-view", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound("Components/Invoke/some-view", new[] { "location1", "location2" })) .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); var result = new ViewViewComponentResult { ViewEngine = viewEngine.Object, ViewName = "some-view", ViewData = viewData, TempData = _tempDataDictionary, }; var viewComponentContext = GetViewComponentContext(view, viewData); // Act and Assert var ex = Assert.Throws(() => result.Execute(viewComponentContext)); Assert.Equal(expected, ex.Message); } [Fact] public void Execute_ThrowsIfPartialViewCannotBeFound_MessageUsesAllLocations() { // Arrange var expected = string.Join(Environment.NewLine, "The view 'Components/Invoke/some-view' was not found. The following locations were searched:", "location1", "location2", "location3", "location4"); var view = Mock.Of(); var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.GetView(/*executingFilePath*/ null, "some-view", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound("some-view", new[] { "location1", "location2" })) .Verifiable(); viewEngine .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/some-view", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound("Components/Invoke/some-view", new[] { "location3", "location4" })) .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); var result = new ViewViewComponentResult { ViewEngine = viewEngine.Object, ViewName = "some-view", ViewData = viewData, TempData = _tempDataDictionary, }; var viewComponentContext = GetViewComponentContext(view, viewData); // Act and Assert var ex = Assert.Throws(() => result.Execute(viewComponentContext)); Assert.Equal(expected, ex.Message); } [Fact] public void Execute_DoesNotWrapThrownExceptionsInAggregateExceptions() { // Arrange var expected = new IndexOutOfRangeException(); var view = new Mock(); view.Setup(v => v.RenderAsync(It.IsAny())) .Throws(expected) .Verifiable(); var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.GetView(/*executingFilePath*/ null, "some-view", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound("some-view", Enumerable.Empty())) .Verifiable(); viewEngine .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/some-view", /*isMainPage*/ false)) .Returns(ViewEngineResult.Found("Components/Invoke/some-view", view.Object)) .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); var result = new ViewViewComponentResult { ViewEngine = viewEngine.Object, ViewName = "some-view", ViewData = viewData, TempData = _tempDataDictionary, }; var viewComponentContext = GetViewComponentContext(view.Object, viewData); // Act var actual = Record.Exception(() => result.Execute(viewComponentContext)); // Assert Assert.Same(expected, actual); view.Verify(); } [Fact] public async Task ExecuteAsync_RendersPartialViews() { // Arrange var view = Mock.Of(); var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.GetView(/*executingFilePath*/ null, "some-view", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound("some-view", Enumerable.Empty())) .Verifiable(); viewEngine .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/some-view", /*isMainPage*/ false)) .Returns(ViewEngineResult.Found("Components/Invoke/some-view", view)) .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); var result = new ViewViewComponentResult { ViewEngine = viewEngine.Object, ViewName = "some-view", ViewData = viewData, TempData = _tempDataDictionary, }; var viewComponentContext = GetViewComponentContext(view, viewData); // Act await result.ExecuteAsync(viewComponentContext); // Assert viewEngine.Verify(); } [Fact] public async Task ExecuteAsync_ResolvesViewEngineFromServiceProvider_IfNoViewEngineIsExplicitlyProvided() { // Arrange var view = Mock.Of(); var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.GetView(/*executingFilePath*/ null, "some-view", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound("some-view", Enumerable.Empty())) .Verifiable(); viewEngine .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/some-view", /*isMainPage*/ false)) .Returns(ViewEngineResult.Found("Components/Invoke/some-view", view)) .Verifiable(); var serviceProvider = new Mock(); serviceProvider.Setup(p => p.GetService(typeof(ICompositeViewEngine))) .Returns(viewEngine.Object); serviceProvider.Setup(p => p.GetService(typeof(DiagnosticSource))) .Returns(new DiagnosticListener("Test")); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); var result = new ViewViewComponentResult { ViewName = "some-view", ViewData = viewData, TempData = _tempDataDictionary, }; var viewComponentContext = GetViewComponentContext(view, viewData); viewComponentContext.ViewContext.HttpContext.RequestServices = serviceProvider.Object; // Act await result.ExecuteAsync(viewComponentContext); // Assert viewEngine.Verify(); serviceProvider.Verify(); } [Fact] public async Task ExecuteAsync_ThrowsIfPartialViewCannotBeFound() { // Arrange var expected = string.Join(Environment.NewLine, "The view 'Components/Invoke/some-view' was not found. The following locations were searched:", "view-location1", "view-location2", "view-location3", "view-location4"); var view = Mock.Of(); var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.GetView(/*executingFilePath*/ null, "some-view", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound("some-view", new[] { "view-location1", "view-location2" })) .Verifiable(); viewEngine .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/some-view", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound( "Components/Invoke/some-view", new[] { "view-location3", "view-location4" })) .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); var result = new ViewViewComponentResult { ViewEngine = viewEngine.Object, ViewName = "some-view", ViewData = viewData, TempData = _tempDataDictionary, }; var viewComponentContext = GetViewComponentContext(view, viewData); // Act and Assert var ex = await Assert.ThrowsAsync( () => result.ExecuteAsync(viewComponentContext)); Assert.Equal(expected, ex.Message); } [Fact] public async Task ExecuteAsync_Throws_IfNoViewEngineCanBeResolved() { // Arrange var expected = $"No service for type '{typeof(ICompositeViewEngine).FullName}'" + " has been registered."; var view = Mock.Of(); var serviceProvider = new ServiceCollection().BuildServiceProvider(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); var result = new ViewViewComponentResult { ViewName = "some-view", ViewData = viewData, TempData = _tempDataDictionary, }; var viewComponentContext = GetViewComponentContext(view, viewData); viewComponentContext.ViewContext.HttpContext.RequestServices = serviceProvider; // Act and Assert var ex = await Assert.ThrowsAsync( () => result.ExecuteAsync(viewComponentContext)); Assert.Equal(expected, ex.Message); } [Theory] [InlineData(null)] [InlineData("")] public void Execute_CallsFindView_WithExpectedPath_WhenViewNameIsNullOrEmpty(string viewName) { // Arrange var shortName = "SomeShortName"; var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); var componentContext = GetViewComponentContext(new Mock().Object, viewData); componentContext.ViewComponentDescriptor.ShortName = shortName; var expectedViewName = $"Components/{shortName}/Default"; var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.FindView(It.IsAny(), expectedViewName, /*isMainPage*/ false)) .Returns(ViewEngineResult.Found(expectedViewName, new Mock().Object)) .Verifiable(); var componentResult = new ViewViewComponentResult(); componentResult.ViewEngine = viewEngine.Object; componentResult.ViewData = viewData; componentResult.ViewName = viewName; componentResult.TempData = _tempDataDictionary; // Act & Assert componentResult.Execute(componentContext); viewEngine.Verify(); } [Theory] [InlineData("~/Home/Index/MyViewComponent1.cshtml")] [InlineData("~MyViewComponent2.cshtml")] [InlineData("/MyViewComponent3.cshtml")] public void Execute_CallsFindView_WithExpectedPath_WhenViewNameIsSpecified(string viewName) { // Arrange var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.GetView(/*executingFilePath*/ null, viewName, /*isMainPage*/ false)) .Returns(ViewEngineResult.Found(viewName, new Mock().Object)) .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); var componentContext = GetViewComponentContext(new Mock().Object, viewData); var componentResult = new ViewViewComponentResult { ViewEngine = viewEngine.Object, ViewData = viewData, ViewName = viewName, TempData = _tempDataDictionary, }; // Act & Assert componentResult.Execute(componentContext); viewEngine.Verify(); } private static ViewComponentContext GetViewComponentContext( IView view, ViewDataDictionary viewData, object diagnosticListener = null) { var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore"); if (diagnosticListener == null) { diagnosticListener = new TestDiagnosticListener(); } diagnosticSource.SubscribeWithAdapter(diagnosticListener); var serviceProvider = new Mock(); serviceProvider.Setup(s => s.GetService(typeof(DiagnosticSource))).Returns(diagnosticSource); var httpContext = new DefaultHttpContext(); httpContext.RequestServices = serviceProvider.Object; var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); var viewContext = new ViewContext( actionContext, view, viewData, new TempDataDictionary(httpContext, new SessionStateTempDataProvider()), TextWriter.Null, new HtmlHelperOptions()); var viewComponentDescriptor = new ViewComponentDescriptor() { ShortName = "Invoke", TypeInfo = typeof(object).GetTypeInfo(), MethodInfo = typeof(object).GetTypeInfo().DeclaredMethods.First() }; var viewComponentContext = new ViewComponentContext( viewComponentDescriptor, new Dictionary(), new HtmlTestEncoder(), viewContext, TextWriter.Null); return viewComponentContext; } } }