// 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.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.PipelineCore; using Microsoft.AspNet.Routing; using Microsoft.AspNet.Testing; using Microsoft.Framework.DependencyInjection; using Moq; using Xunit; namespace Microsoft.AspNet.Mvc { public class ControllerActionInvokerTest { // Intentionally choosing an uncommon exception type. private readonly Exception _actionException = new TimeZoneNotFoundException(); private readonly JsonResult _result = new JsonResult(new { message = "Hello, world!" }); private struct SampleStruct { public int x; } [Fact] public async Task InvokeAction_DoesNotInvokeExceptionFilter_WhenActionDoesNotThrow() { // Arrange var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnException(It.IsAny())) .Verifiable(); var invoker = CreateInvoker(filter.Object, actionThrows: false); // Act await invoker.InvokeAsync(); // Assert filter.Verify(f => f.OnException(It.IsAny()), Times.Never()); } [Fact] public async Task InvokeAction_DoesNotAsyncInvokeExceptionFilter_WhenActionDoesNotThrow() { // Arrange var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnExceptionAsync(It.IsAny())) .Returns((context) => Task.FromResult(true)) .Verifiable(); var invoker = CreateInvoker(filter.Object, actionThrows: false); // Act await invoker.InvokeAsync(); // Assert filter.Verify( f => f.OnExceptionAsync(It.IsAny()), Times.Never()); } [Fact] public async Task InvokeAction_InvokesExceptionFilter_WhenActionThrows() { // Arrange Exception exception = null; IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnException(It.IsAny())) .Callback(context => { exception = context.Exception; result = context.Result; // Handle the exception context.Result = new EmptyResult(); }) .Verifiable(); var invoker = CreateInvoker(filter.Object, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert filter.Verify(f => f.OnException(It.IsAny()), Times.Once()); Assert.Same(_actionException, exception); Assert.Null(result); } [Fact] public async Task InvokeAction_InvokesAsyncExceptionFilter_WhenActionThrows() { // Arrange Exception exception = null; IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnExceptionAsync(It.IsAny())) .Callback(context => { exception = context.Exception; result = context.Result; // Handle the exception context.Result = new EmptyResult(); }) .Returns((context) => Task.FromResult(true)) .Verifiable(); var invoker = CreateInvoker(filter.Object, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert filter.Verify( f => f.OnExceptionAsync(It.IsAny()), Times.Once()); Assert.Same(_actionException, exception); Assert.Null(result); } [Fact] public async Task InvokeAction_InvokesExceptionFilter_ShortCircuit() { // Arrange var filter1 = new Mock(MockBehavior.Strict); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnException(It.IsAny())) .Callback(context => { filter2.ToString(); context.Exception = null; }) .Verifiable(); var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert filter2.Verify( f => f.OnException(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncExceptionFilter_ShortCircuit() { // Arrange var filter1 = new Mock(MockBehavior.Strict); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnExceptionAsync(It.IsAny())) .Callback(context => { filter2.ToString(); context.Exception = null; }) .Returns((context) => Task.FromResult(true)) .Verifiable(); var invoker = CreateInvoker(new IFilter[] { filter1.Object, filter2.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert filter2.Verify( f => f.OnExceptionAsync(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesExceptionFilter_UnhandledExceptionIsThrown() { // Arrange var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnException(It.IsAny())) .Verifiable(); var invoker = CreateInvoker(filter.Object, actionThrows: true); // Act await Assert.ThrowsAsync(_actionException.GetType(), async () => await invoker.InvokeAsync()); // Assert filter.Verify(f => f.OnException(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesExceptionFilter_ResultIsExecuted_WithoutResultFilters() { // Arrange var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns((context) => Task.FromResult(true)) .Verifiable(); var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnException(It.IsAny())) .Callback(c => c.Result = result.Object) .Verifiable(); var resultFilter = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new IFilter[] { filter.Object, resultFilter.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert filter.Verify(f => f.OnException(It.IsAny()), Times.Once()); result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAuthorizationFilter() { // Arrange var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnAuthorization(It.IsAny())).Verifiable(); var invoker = CreateInvoker(filter.Object); // Act await invoker.InvokeAsync(); // Assert filter.Verify(f => f.OnAuthorization(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncAuthorizationFilter() { // Arrange var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnAuthorizationAsync(It.IsAny())) .Returns(context => Task.FromResult(true)) .Verifiable(); var invoker = CreateInvoker(filter.Object); // Act await invoker.InvokeAsync(); // Assert filter.Verify( f => f.OnAuthorizationAsync(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAuthorizationFilter_ShortCircuit() { // Arrange var challenge = new Mock(MockBehavior.Loose).Object; var filter1 = new Mock(MockBehavior.Strict); filter1 .Setup(f => f.OnAuthorization(It.IsAny())) .Callback(c => c.Result = challenge) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }); // Act await invoker.InvokeAsync(); // Assert filter1.Verify(f => f.OnAuthorization(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncAuthorizationFilter_ShortCircuit() { // Arrange var challenge = new Mock(MockBehavior.Loose).Object; var filter1 = new Mock(MockBehavior.Strict); filter1 .Setup(f => f.OnAuthorizationAsync(It.IsAny())) .Returns((context) => { context.Result = challenge; return Task.FromResult(true); }) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new IFilter[] { filter1.Object, filter2.Object }); // Act await invoker.InvokeAsync(); // Assert filter1.Verify( f => f.OnAuthorizationAsync(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_ExceptionInAuthorizationFilterHandledByExceptionFilters() { // Arrange Exception exception = null; var expected = new InvalidCastException(); var exceptionFilter = new Mock(MockBehavior.Strict); exceptionFilter .Setup(f => f.OnException(It.IsAny())) .Callback(context => { exception = context.Exception; // Mark as handled context.Result = new EmptyResult(); }) .Verifiable(); var authorizationFilter1 = new Mock(MockBehavior.Strict); authorizationFilter1 .Setup(f => f.OnAuthorization(It.IsAny())) .Callback(c => { throw expected; }) .Verifiable(); // None of these filters should run var authorizationFilter2 = new Mock(MockBehavior.Strict); var actionFilter = new Mock(MockBehavior.Strict); var resultFilter = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new IFilter[] { exceptionFilter.Object, authorizationFilter1.Object, authorizationFilter2.Object, actionFilter.Object, resultFilter.Object, }); // Act await invoker.InvokeAsync(); // Assert exceptionFilter.Verify(f => f.OnException(It.IsAny()), Times.Once()); authorizationFilter1.Verify(f => f.OnAuthorization(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAuthorizationFilter_ChallengeNotSeenByResultFilters() { // Arrange var challenge = new Mock(MockBehavior.Strict); challenge .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns((context) => Task.FromResult(true)) .Verifiable(); var authorizationFilter = new Mock(MockBehavior.Strict); authorizationFilter .Setup(f => f.OnAuthorization(It.IsAny())) .Callback(c => c.Result = challenge.Object) .Verifiable(); var resultFilter = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new IFilter[] { authorizationFilter.Object, resultFilter.Object }); // Act await invoker.InvokeAsync(); // Assert authorizationFilter.Verify(f => f.OnAuthorization(It.IsAny()), Times.Once()); challenge.Verify(c => c.ExecuteResultAsync(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesActionFilter() { // Arrange IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker(filter.Object); // Act await invoker.InvokeAsync(); // Assert filter.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); filter.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); Assert.Same(_result, result); } [Fact] public async Task InvokeAction_InvokesAsyncActionFilter() { // Arrange IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (context, next) => { var resultContext = await next(); result = resultContext.Result; }) .Verifiable(); var invoker = CreateInvoker(filter.Object); // Act await invoker.InvokeAsync(); // Assert filter.Verify( f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); Assert.Same(_result, result); } [Fact] public async Task InvokeAction_InvokesActionFilter_ShortCircuit() { // Arrange var result = new EmptyResult(); ActionExecutedContext context = null; var actionFilter1 = new Mock(MockBehavior.Strict); actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); actionFilter1 .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => context = c) .Verifiable(); var actionFilter2 = new Mock(MockBehavior.Strict); actionFilter2 .Setup(f => f.OnActionExecuting(It.IsAny())) .Callback(c => c.Result = result) .Verifiable(); var actionFilter3 = new Mock(MockBehavior.Strict); var resultFilter = new Mock(MockBehavior.Strict); resultFilter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); resultFilter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); var invoker = CreateInvoker(new IFilter[] { actionFilter1.Object, actionFilter2.Object, actionFilter3.Object, resultFilter.Object, }); // Act await invoker.InvokeAsync(); // Assert actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); actionFilter2.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); resultFilter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); resultFilter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); Assert.True(context.Canceled); Assert.Same(context.Result, result); } [Fact] public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithResult() { // Arrange var result = new EmptyResult(); ActionExecutedContext context = null; var actionFilter1 = new Mock(MockBehavior.Strict); actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); actionFilter1 .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => context = c) .Verifiable(); var actionFilter2 = new Mock(MockBehavior.Strict); actionFilter2 .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) .Returns((c, next) => { // Notice we're not calling next c.Result = result; return Task.FromResult(true); }) .Verifiable(); var actionFilter3 = new Mock(MockBehavior.Strict); var resultFilter = new Mock(MockBehavior.Strict); resultFilter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); resultFilter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); var invoker = CreateInvoker(new IFilter[] { actionFilter1.Object, actionFilter2.Object, actionFilter3.Object, resultFilter.Object, }); // Act await invoker.InvokeAsync(); // Assert actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); actionFilter2.Verify( f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); resultFilter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); resultFilter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); Assert.True(context.Canceled); Assert.Same(context.Result, result); } [Fact] public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithoutResult() { // Arrange ActionExecutedContext context = null; var actionFilter1 = new Mock(MockBehavior.Strict); actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); actionFilter1 .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => context = c) .Verifiable(); var actionFilter2 = new Mock(MockBehavior.Strict); actionFilter2 .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) .Returns((c, next) => { // Notice we're not calling next return Task.FromResult(true); }) .Verifiable(); var actionFilter3 = new Mock(MockBehavior.Strict); var resultFilter = new Mock(MockBehavior.Strict); resultFilter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); resultFilter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); var invoker = CreateInvoker(new IFilter[] { actionFilter1.Object, actionFilter2.Object, actionFilter3.Object, resultFilter.Object, }); // Act await invoker.InvokeAsync(); // Assert actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); actionFilter2.Verify( f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); resultFilter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); resultFilter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); Assert.True(context.Canceled); Assert.Null(context.Result); } [Fact] public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithResult_CallNext() { // Arrange var actionFilter = new Mock(MockBehavior.Strict); actionFilter .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { c.Result = new EmptyResult(); await next(); }) .Verifiable(); var message = "If an IAsyncActionFilter provides a result value by setting the Result property of " + "ActionExecutingContext to a non-null value, then it cannot call the next filter by invoking " + "ActionExecutionDelegate."; var invoker = CreateInvoker(actionFilter.Object); // Act & Assert await ExceptionAssert.ThrowsAsync( async () => await invoker.InvokeAsync(), message); } [Fact] public async Task InvokeAction_InvokesActionFilter_WithExceptionThrownByAction() { // Arrange ActionExecutedContext context = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => { context = c; // Handle the exception so the test doesn't throw. Assert.False(c.ExceptionHandled); c.ExceptionHandled = true; }) .Verifiable(); var invoker = CreateInvoker(filter.Object, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert filter.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); filter.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); Assert.Same(_actionException, context.Exception); Assert.Null(context.Result); } [Fact] public async Task InvokeAction_InvokesActionFilter_WithExceptionThrownByActionFilter() { // Arrange var exception = new DataMisalignedException(); ActionExecutedContext context = null; var filter1 = new Mock(MockBehavior.Strict); filter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter1 .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => { context = c; // Handle the exception so the test doesn't throw. Assert.False(c.ExceptionHandled); c.ExceptionHandled = true; }) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); filter2.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter2 .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => { throw exception; }) .Verifiable(); var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }); // Act await invoker.InvokeAsync(); // Assert filter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); filter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); filter2.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); Assert.Same(exception, context.Exception); Assert.Null(context.Result); } [Fact] public async Task InvokeAction_InvokesAsyncActionFilter_WithExceptionThrownByActionFilter() { // Arrange var exception = new DataMisalignedException(); ActionExecutedContext context = null; var filter1 = new Mock(MockBehavior.Strict); filter1 .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { context = await next(); // Handle the exception so the test doesn't throw. Assert.False(context.ExceptionHandled); context.ExceptionHandled = true; }) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); filter2.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter2 .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => { throw exception; }) .Verifiable(); var invoker = CreateInvoker(new IFilter[] { filter1.Object, filter2.Object }); // Act await invoker.InvokeAsync(); // Assert filter1.Verify( f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); filter2.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); Assert.Same(exception, context.Exception); Assert.Null(context.Result); } [Fact] public async Task InvokeAction_InvokesActionFilter_HandleException() { // Arrange var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns((context) => Task.FromResult(true)) .Verifiable(); var actionFilter = new Mock(MockBehavior.Strict); actionFilter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); actionFilter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => { // Handle the exception so the test doesn't throw. Assert.False(c.ExceptionHandled); c.ExceptionHandled = true; c.Result = result.Object; }) .Verifiable(); var resultFilter = new Mock(MockBehavior.Strict); resultFilter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); resultFilter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); var invoker = CreateInvoker(new IFilter[] { actionFilter.Object, resultFilter.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert actionFilter.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); actionFilter.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); resultFilter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); resultFilter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesResultFilter() { // Arrange var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); filter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); var invoker = CreateInvoker(filter.Object); // Act await invoker.InvokeAsync(); // Assert filter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); filter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncResultFilter() { // Arrange var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (context, next) => await next()) .Verifiable(); var invoker = CreateInvoker(filter.Object); // Act await invoker.InvokeAsync(); // Assert filter.Verify( f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesResultFilter_ShortCircuit() { // Arrange ResultExecutedContext context = null; var filter1 = new Mock(MockBehavior.Strict); filter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); filter1 .Setup(f => f.OnResultExecuted(It.IsAny())) .Callback(c => context = c) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnResultExecuting(It.IsAny())) .Callback(c => { filter2.ToString(); c.Cancel = true; }) .Verifiable(); var filter3 = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new IFilter[] { filter1.Object, filter2.Object, filter3.Object }); // Act await invoker.InvokeAsync(); // Assert filter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); filter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); filter2.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); Assert.True(context.Canceled); } [Fact] public async Task InvokeAction_InvokesAsyncResultFilter_ShortCircuit_WithCancel() { // Arrange ResultExecutedContext context = null; var filter1 = new Mock(MockBehavior.Strict); filter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); filter1 .Setup(f => f.OnResultExecuted(It.IsAny())) .Callback(c => context = c) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) .Returns((c, next) => { // Not calling next here c.Cancel = true; return Task.FromResult(true); }) .Verifiable(); var filter3 = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new IFilter[] { filter1.Object, filter2.Object, filter3.Object }); // Act await invoker.InvokeAsync(); // Assert filter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); filter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); filter2.Verify( f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); Assert.True(context.Canceled); Assert.IsType(context.Result); } [Fact] public async Task InvokeAction_InvokesAsyncResultFilter_ShortCircuit_WithoutCancel() { // Arrange ResultExecutedContext context = null; var filter1 = new Mock(MockBehavior.Strict); filter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); filter1 .Setup(f => f.OnResultExecuted(It.IsAny())) .Callback(c => context = c) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) .Returns((c, next) => { // Not calling next here return Task.FromResult(true); }) .Verifiable(); var filter3 = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new IFilter[] { filter1.Object, filter2.Object, filter3.Object }); // Act await invoker.InvokeAsync(); // Assert filter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); filter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); filter2.Verify( f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); Assert.True(context.Canceled); Assert.IsType(context.Result); } [Fact] public async Task InvokeAction_InvokesAsyncResultFilter_ShortCircuit_WithoutCancel_CallNext() { // Arrange var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { // Not calling next here c.Cancel = true; await next(); }) .Verifiable(); var message = "If an IAsyncResultFilter cancels execution by setting the Cancel property of " + "ResultExecutingContext to 'true', then it cannot call the next filter by invoking " + "ResultExecutionDelegate."; var invoker = CreateInvoker(filter.Object); // Act & Assert await ExceptionAssert.ThrowsAsync( async () => await invoker.InvokeAsync(), message); } [Fact] public async Task InvokeAction_InvokesResultFilter_ExceptionGoesUnhandled() { // Arrange var exception = new DataMisalignedException(); var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Throws(exception) .Verifiable(); var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnResultExecuting(It.IsAny())) .Callback(c => c.Result = result.Object) .Verifiable(); filter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); var invoker = CreateInvoker(filter.Object); // Act await Assert.ThrowsAsync(exception.GetType(), async () => await invoker.InvokeAsync()); // Assert result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); filter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); filter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesResultFilter_WithExceptionThrownByResult() { // Arrange ResultExecutedContext context = null; var exception = new DataMisalignedException(); var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Throws(exception) .Verifiable(); var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnResultExecuting(It.IsAny())) .Callback(c => c.Result = result.Object) .Verifiable(); filter .Setup(f => f.OnResultExecuted(It.IsAny())) .Callback(c => { context = c; // Handle the exception Assert.False(c.ExceptionHandled); c.ExceptionHandled = true; }) .Verifiable(); var invoker = CreateInvoker(filter.Object); // Act await invoker.InvokeAsync(); // Assert Assert.Equal(exception, context.Exception); result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); filter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); filter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncResultFilter_WithExceptionThrownByResult() { // Arrange ResultExecutedContext context = null; var exception = new DataMisalignedException(); var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Throws(exception) .Verifiable(); var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { c.Result = result.Object; context = await next(); // Handle the exception Assert.False(context.ExceptionHandled); context.ExceptionHandled = true; }) .Verifiable(); var invoker = CreateInvoker(filter.Object); // Act await invoker.InvokeAsync(); // Assert Assert.Equal(exception, context.Exception); result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); filter.Verify( f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesResultFilter_WithExceptionThrownByResultFilter() { // Arrange ResultExecutedContext context = null; var exception = new DataMisalignedException(); var resultFilter1 = new Mock(MockBehavior.Strict); resultFilter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); resultFilter1 .Setup(f => f.OnResultExecuted(It.IsAny())) .Callback(c => { context = c; // Handle the exception Assert.False(c.ExceptionHandled); c.ExceptionHandled = true; }) .Verifiable(); var resultFilter2 = new Mock(MockBehavior.Strict); resultFilter2 .Setup(f => f.OnResultExecuting(It.IsAny())) .Throws(exception) .Verifiable(); var resultFilter3 = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new IFilter[] { resultFilter1.Object, resultFilter2.Object, resultFilter3.Object }); // Act await invoker.InvokeAsync(); // Assert Assert.Equal(exception, context.Exception); resultFilter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); resultFilter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); resultFilter2.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncResultFilter_WithExceptionThrownByResultFilter() { // Arrange ResultExecutedContext context = null; var exception = new DataMisalignedException(); var resultFilter1 = new Mock(MockBehavior.Strict); resultFilter1 .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { context = await next(); // Handle the exception Assert.False(context.ExceptionHandled); context.ExceptionHandled = true; }) .Verifiable(); var resultFilter2 = new Mock(MockBehavior.Strict); resultFilter2 .Setup(f => f.OnResultExecuting(It.IsAny())) .Throws(exception) .Verifiable(); var resultFilter3 = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new IFilter[] { resultFilter1.Object, resultFilter2.Object, resultFilter3.Object }); // Act await invoker.InvokeAsync(); // Assert Assert.Equal(exception, context.Exception); resultFilter1.Verify( f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); resultFilter2.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); } [Fact] public void CreateActionResult_ReturnsSameActionResult() { // Arrange var mockActionResult = new Mock(); // Assert var result = ControllerActionInvoker.CreateActionResult( mockActionResult.Object.GetType(), mockActionResult.Object); // Act Assert.Same(mockActionResult.Object, result); } [Fact] [ReplaceCulture] public void CreateActionResult_NullActionResultReturnValueThrows() { // Arrange, Act & Assert ExceptionAssert.Throws( () => ControllerActionInvoker.CreateActionResult(typeof(IActionResult), null), "Cannot return null from an action method with a return type of '" + typeof(IActionResult) + "'."); } [Theory] [InlineData(typeof(void))] [InlineData(typeof(Task))] public void CreateActionResult_Types_ReturnsObjectResultForTaskAndVoidReturnTypes(Type type) { // Arrange & Act var result = ControllerActionInvoker.CreateActionResult(type, null); // Assert var objectResult = Assert.IsType(result); // Since we unwrap the Task type to void, the expected type will always be void. Assert.Equal(typeof(void), objectResult.DeclaredType); Assert.Null(objectResult.Value); } public static IEnumerable CreateActionResult_ReturnsObjectContentResultData { get { var anonymousObject = new { x1 = 10, y1 = "Hello" }; yield return new object[] { anonymousObject.GetType(), anonymousObject, }; yield return new object[] { typeof(int), 5 }; yield return new object[] { typeof(string), "sample input" }; SampleStruct test; test.x = 10; yield return new object[] { test.GetType(), test }; yield return new object[] { typeof(Task), 5 }; yield return new object[] { typeof(Task), "Hello world" }; } } [Theory] [MemberData(nameof(CreateActionResult_ReturnsObjectContentResultData))] public void CreateActionResult_ReturnsObjectContentResult(Type type, object input) { // Arrange & Act var actualResult = ControllerActionInvoker.CreateActionResult(type, input); // Assert var contentResult = Assert.IsType(actualResult); Assert.Same(input, contentResult.Value); } private TestControllerActionInvoker CreateInvoker(IFilter filter, bool actionThrows = false) { return CreateInvoker(new[] { filter }, actionThrows); } private TestControllerActionInvoker CreateInvoker(IFilter[] filters, bool actionThrows = false) { var actionDescriptor = new ControllerActionDescriptor() { FilterDescriptors = new List(), Parameters = new List(), }; if (actionThrows) { actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod("ThrowingActionMethod"); } else { actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod("ActionMethod"); } var httpContext = new Mock(MockBehavior.Loose); var httpResponse = new Mock(MockBehavior.Loose); var mockFormattersProvider = new Mock(); mockFormattersProvider.SetupGet(o => o.OutputFormatters) .Returns( new List() { new JsonOutputFormatter() }); httpContext.SetupGet(o => o.Request.Accept) .Returns(""); httpContext.SetupGet(c => c.Response).Returns(httpResponse.Object); httpContext.Setup(o => o.RequestServices.GetService(typeof(IOutputFormattersProvider))) .Returns(mockFormattersProvider.Object); httpResponse.SetupGet(r => r.Body).Returns(new MemoryStream()); var actionContext = new ActionContext( httpContext: httpContext.Object, routeData: new RouteData(), actionDescriptor: actionDescriptor); var controllerFactory = new Mock(); controllerFactory.Setup(c => c.CreateController(It.IsAny())).Returns(this); controllerFactory.Setup(m => m.ReleaseController(this)).Verifiable(); var actionBindingContextProvider = new Mock(MockBehavior.Strict); actionBindingContextProvider .Setup(abcp => abcp.GetActionBindingContextAsync(It.IsAny())) .Returns(Task.FromResult(new ActionBindingContext(null, null, null, null, null, null))); var filterProvider = new Mock>(MockBehavior.Strict); filterProvider .Setup(fp => fp.Invoke(It.IsAny())) .Callback( context => context.Results.AddRange(filters.Select(f => new FilterItem(null, f)))); var inputFormattersProvider = new Mock(); inputFormattersProvider.SetupGet(o => o.InputFormatters) .Returns(new List()); var invoker = new TestControllerActionInvoker( actionContext, filterProvider.Object, controllerFactory, actionDescriptor, inputFormattersProvider.Object, Mock.Of()); return invoker; } [Fact] public async Task Invoke_UsesDefaultValuesIfNotBound() { // Arrange var actionDescriptor = new ControllerActionDescriptor { MethodInfo = typeof(TestController).GetTypeInfo() .DeclaredMethods .First(m => m.Name.Equals("ActionMethodWithDefaultValues", StringComparison.Ordinal)), Parameters = new List { new ParameterDescriptor { Name = "value", ParameterBindingInfo = new ParameterBindingInfo("value", typeof(int)) } }, FilterDescriptors = new List() }; var binder = new Mock(); var metadataProvider = new EmptyModelMetadataProvider(); binder.Setup(b => b.BindModelAsync(It.IsAny())) .Returns(Task.FromResult(result: false)); var context = new Mock(); context.SetupGet(c => c.Items) .Returns(new Dictionary()); var routeContext = new RouteContext(context.Object); var actionContext = new ActionContext(routeContext, actionDescriptor); var bindingContext = new ActionBindingContext(actionContext, Mock.Of(), binder.Object, Mock.Of(), Mock.Of(), Mock.Of()); var actionBindingContextProvider = new Mock(); actionBindingContextProvider.Setup(p => p.GetActionBindingContextAsync(It.IsAny())) .Returns(Task.FromResult(bindingContext)); var controllerFactory = new Mock(); controllerFactory.Setup(c => c.CreateController(It.IsAny())) .Returns(new TestController()); var inputFormattersProvider = new Mock(); inputFormattersProvider.SetupGet(o => o.InputFormatters) .Returns(new List()); var invoker = new ControllerActionInvoker(actionContext, Mock.Of>(), controllerFactory.Object, actionDescriptor, inputFormattersProvider.Object, new DefaultControllerActionArgumentBinder( actionBindingContextProvider.Object, new DefaultBodyModelValidator())); // Act await invoker.InvokeAsync(); // Assert Assert.Equal(5, context.Object.Items["Result"]); } public JsonResult ActionMethod() { return _result; } public JsonResult ThrowingActionMethod() { throw _actionException; } public JsonResult ActionMethodWithBodyParameter([FromBody] Person bodyParam) { return new JsonResult(bodyParam); } public class Person { public string Name { get; set; } public int Age { get; set; } } private sealed class TestController { public IActionResult ActionMethodWithDefaultValues(int value = 5) { return new TestActionResult { Value = value }; } } private sealed class TestActionResult : IActionResult { public int Value { get; set; } public Task ExecuteResultAsync(ActionContext context) { context.HttpContext.Items["Result"] = Value; return Task.FromResult(0); } } public class TestControllerActionInvoker : ControllerActionInvoker { private Mock _factoryMock; public TestControllerActionInvoker( ActionContext actionContext, INestedProviderManager filterProvider, Mock controllerFactoryMock, ControllerActionDescriptor descriptor, IInputFormattersProvider inputFormattersProvider, IControllerActionArgumentBinder controllerActionArgumentBinder) : base(actionContext, filterProvider, controllerFactoryMock.Object, descriptor, inputFormattersProvider, controllerActionArgumentBinder) { _factoryMock = controllerFactoryMock; } public async override Task InvokeAsync() { await base.InvokeAsync(); _factoryMock.Verify(); } } } }