diff --git a/Settings.StyleCop b/Settings.StyleCop
new file mode 100644
index 0000000..b8dcb05
--- /dev/null
+++ b/Settings.StyleCop
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/TestStack.FluentMVCTesting.Tests/MvcPipeline/PipelineExecutorTests.cs b/TestStack.FluentMVCTesting.Tests/MvcPipeline/PipelineExecutorTests.cs
new file mode 100644
index 0000000..f34df24
--- /dev/null
+++ b/TestStack.FluentMVCTesting.Tests/MvcPipeline/PipelineExecutorTests.cs
@@ -0,0 +1,34 @@
+using System.Web.Mvc;
+using NUnit.Framework;
+using TestStack.FluentMVCTesting.MvcPipeline;
+using TestStack.FluentMVCTesting.Tests.TestControllers;
+
+namespace TestStack.FluentMVCTesting.Tests.MvcPipeline
+{
+ [TestFixture]
+ class PipelineExecutorTests
+ {
+ [Test]
+ public void should_call_filters()
+ {
+ var controller = new ControllerExtensionsController();
+ GlobalFilters.Filters.Add(new DummyFilter());
+ var executor = new PipelineActionExecutor();
+
+ var result = executor.Execute(controller, c => c.SomeAction());
+
+ Assert.That(DummyFilter.LoggingFilterWasCalled);
+ }
+
+ private class DummyFilter : ActionFilterAttribute, IActionFilter
+ {
+ public static bool LoggingFilterWasCalled { get; set; }
+
+ public override void OnActionExecuting(ActionExecutingContext filterContext)
+ {
+ LoggingFilterWasCalled = true;
+ }
+ }
+ }
+
+}
diff --git a/TestStack.FluentMVCTesting.Tests/TestStack.FluentMVCTesting.Tests.csproj b/TestStack.FluentMVCTesting.Tests/TestStack.FluentMVCTesting.Tests.csproj
index 5129022..9638acd 100644
--- a/TestStack.FluentMVCTesting.Tests/TestStack.FluentMVCTesting.Tests.csproj
+++ b/TestStack.FluentMVCTesting.Tests/TestStack.FluentMVCTesting.Tests.csproj
@@ -82,6 +82,7 @@
+
diff --git a/TestStack.FluentMvcTesting/ControllerExtensions.cs b/TestStack.FluentMvcTesting/ControllerExtensions.cs
index 4c377c8..b70bff8 100644
--- a/TestStack.FluentMvcTesting/ControllerExtensions.cs
+++ b/TestStack.FluentMvcTesting/ControllerExtensions.cs
@@ -7,7 +7,6 @@ namespace TestStack.FluentMVCTesting
{
public static class ControllerExtensions
{
-
public static T WithModelErrors(this T controller) where T : Controller
{
controller.ModelState.AddModelError("Key", "Value");
@@ -18,22 +17,17 @@ public static ControllerResultTest WithCallTo(this T controller,
where T : Controller
where TAction : ActionResult
{
- var actionName = ((MethodCallExpression)actionCall.Body).Method.Name;
-
- var actionResult = actionCall.Compile().Invoke(controller);
-
- return new ControllerResultTest(controller, actionName, actionResult);
+ //var actionExecutor = new PipelineActionExecutor();
+ var actionExecutor = new SimpleActionExecutor();
+ return actionExecutor.Execute(controller, actionCall);
}
public static ControllerResultTest WithCallTo(this T controller, Expression>> actionCall)
where T : Controller
where TAction : ActionResult
{
- var actionName = ((MethodCallExpression)actionCall.Body).Method.Name;
-
- var actionResult = actionCall.Compile().Invoke(controller).Result;
-
- return new ControllerResultTest(controller, actionName, actionResult);
+ var actionExecutor = new SimpleActionExecutor();
+ return actionExecutor.Execute(controller, actionCall);
}
public static ControllerResultTest WithCallToChild(this T controller, Expression> actionCall)
diff --git a/TestStack.FluentMvcTesting/IActionExecutor.cs b/TestStack.FluentMvcTesting/IActionExecutor.cs
new file mode 100644
index 0000000..4d8127c
--- /dev/null
+++ b/TestStack.FluentMvcTesting/IActionExecutor.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+using System.Web.Mvc;
+
+namespace TestStack.FluentMVCTesting
+{
+ public interface IActionExecutor
+ {
+ ControllerResultTest Execute(TController controller, Expression> actionCall)
+ where TController : Controller
+ where TAction : ActionResult;
+
+ ControllerResultTest Execute(TController controller, Expression>> actionCall)
+ where TController : Controller
+ where TAction : ActionResult;
+ }
+}
\ No newline at end of file
diff --git a/TestStack.FluentMvcTesting/MvcPipeline/ActionContext.cs b/TestStack.FluentMvcTesting/MvcPipeline/ActionContext.cs
new file mode 100644
index 0000000..7ecd93a
--- /dev/null
+++ b/TestStack.FluentMvcTesting/MvcPipeline/ActionContext.cs
@@ -0,0 +1,10 @@
+using System.Web.Mvc;
+
+namespace TestStack.FluentMVCTesting.MvcPipeline
+{
+ public class ActionContext
+ {
+ public ControllerContext ControllerContext { get; set; }
+ public ActionDescriptor ActionDescriptor { get; set; }
+ }
+}
diff --git a/TestStack.FluentMvcTesting/MvcPipeline/ActionRequestWrapper.cs b/TestStack.FluentMvcTesting/MvcPipeline/ActionRequestWrapper.cs
new file mode 100644
index 0000000..76f6e3f
--- /dev/null
+++ b/TestStack.FluentMvcTesting/MvcPipeline/ActionRequestWrapper.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Web;
+
+namespace TestStack.FluentMVCTesting.MvcPipeline
+{
+ internal class ActionRequestWrapper : HttpWorkerRequest
+ {
+ private readonly SimpleHttpRequest _httpRequest;
+
+ public ActionRequestWrapper(SimpleHttpRequest httpRequest)
+ {
+ this._httpRequest = httpRequest;
+ }
+
+ public override string GetUriPath()
+ {
+ var path = this._httpRequest.UriPath;
+ if (path.StartsWith("~"))
+ path = path.Substring(1);
+ return path;
+ }
+
+ public override string GetQueryString()
+ {
+ return String.Empty;
+ }
+
+ public override string GetRawUrl()
+ {
+ return this.GetUriPath();
+ }
+
+ public override string GetHttpVerbName()
+ {
+ return this._httpRequest.HttpMethod;
+ }
+
+ public override string GetHttpVersion()
+ {
+ return "HTTP/1.1";
+ }
+
+ public override string GetRemoteAddress()
+ {
+ return "localhost";
+ }
+
+ public override int GetRemotePort()
+ {
+ throw new NotImplementedException();
+ }
+
+ public override string GetLocalAddress()
+ {
+ return "127.0.0.1";
+ }
+
+ public override int GetLocalPort()
+ {
+ return 80;
+ }
+
+ public override void SendStatus(int statusCode, string statusDescription)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void SendKnownResponseHeader(int index, string value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void SendUnknownResponseHeader(string name, string value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void SendResponseFromMemory(byte[] data, int length)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void SendResponseFromFile(string filename, long offset, long length)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void SendResponseFromFile(IntPtr handle, long offset, long length)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void FlushResponse(bool finalFlush)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void EndOfRequest()
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/TestStack.FluentMvcTesting/MvcPipeline/ControllerActionBase.cs b/TestStack.FluentMvcTesting/MvcPipeline/ControllerActionBase.cs
new file mode 100644
index 0000000..764e2a7
--- /dev/null
+++ b/TestStack.FluentMvcTesting/MvcPipeline/ControllerActionBase.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Security.Principal;
+using System.Web;
+using System.Web.Mvc;
+
+namespace TestStack.FluentMVCTesting.MvcPipeline
+{
+ public abstract class ControllerActionBase
+ {
+ protected ControllerActionBase()
+ {
+ FilterProviders = new FilterProviderCollection(System.Web.Mvc.FilterProviders.Providers);
+ Cookies = new Collection();
+ Session = new Dictionary();
+ Files = new Dictionary();
+ Resolve = DependencyResolver.Current.GetService;
+ }
+
+ public FilterProviderCollection FilterProviders { get; private set; }
+
+ public IPrincipal User { get; set; }
+
+ public IValueProvider ValueProvider { get; set; }
+
+ public ICollection Cookies { get; private set; }
+ public IDictionary Session { get; private set; }
+ public IDictionary Files { get; private set; }
+ public IDictionary Form { get; private set; }
+ public Func Resolve { get; set; }
+
+ public string HttpMethod { get; set; }
+
+ public string UriPath { get; set; }
+
+ public abstract ActionContext GetActionContext(HttpContextBase httpContext = null);
+
+ public virtual ControllerActionResult Execute(HttpContextBase httpContext = null)
+ {
+ var actionContext = GetActionContext(httpContext ?? CreateHttpContext());
+ return Execute(actionContext.ControllerContext, actionContext.ActionDescriptor);
+ }
+
+ protected virtual ControllerActionResult Execute(ControllerContext controllerContext, System.Web.Mvc.ActionDescriptor actionDescriptor)
+ {
+ var invoker = GetActionInvoker(controllerContext, actionDescriptor);
+ return new ControllerActionResult
+ {
+ ControllerContext = controllerContext,
+ ActionResult = invoker.InvokeAction()
+ };
+ }
+
+ internal virtual MvcActionInvoker GetActionInvoker(ControllerContext controllerContext,
+ System.Web.Mvc.ActionDescriptor actionDescriptor)
+ {
+ var filters = FilterProviders.GetFilters(controllerContext, actionDescriptor).Select(BuildUp);
+
+ return new MvcActionInvoker(controllerContext, actionDescriptor, filters);
+ }
+
+ protected virtual Filter BuildUp(Filter filter)
+ {
+ if (Resolve == null)
+ return filter;
+
+ foreach (PropertyDescriptor propertyDesc in TypeDescriptor.GetProperties(filter.Instance))
+ {
+ var typeNames = propertyDesc.Attributes.OfType().Select(e => e.GetType().ToString());
+ if (typeNames.Contains("Microsoft.Practices.Unity.DependencyAttribute"))
+ {
+ var service = Resolve(propertyDesc.PropertyType);
+ if (service != null)
+ propertyDesc.SetValue(filter.Instance, service);
+ }
+ }
+
+ return filter;
+ }
+
+ public virtual ActionResult Authorize()
+ {
+ var actionContext = GetActionContext(CreateHttpContext());
+ return Authorize(actionContext.ControllerContext, actionContext.ActionDescriptor);
+ }
+
+ protected virtual ActionResult Authorize(ControllerContext controllerContext, System.Web.Mvc.ActionDescriptor actionDescriptor)
+ {
+ return GetActionInvoker(controllerContext, actionDescriptor).AuthorizeAction();
+ }
+
+ public abstract HttpContextBase CreateHttpContext();
+ }
+
+ [Serializable]
+ public class ControllerActionException : Exception
+ {
+ public ControllerActionException(string message)
+ : base(message)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/TestStack.FluentMvcTesting/MvcPipeline/ControllerActionResult.cs b/TestStack.FluentMvcTesting/MvcPipeline/ControllerActionResult.cs
new file mode 100644
index 0000000..b371724
--- /dev/null
+++ b/TestStack.FluentMvcTesting/MvcPipeline/ControllerActionResult.cs
@@ -0,0 +1,46 @@
+using System.Web;
+using System.Web.Mvc;
+
+namespace TestStack.FluentMVCTesting.MvcPipeline
+{
+ public class ControllerActionResult
+ {
+ public ControllerContext ControllerContext { get; set; }
+ public ActionResult ActionResult { get; set; }
+
+ public dynamic ViewBag
+ {
+ get { return ControllerContext.Controller.ViewBag; }
+ }
+
+ public ViewDataDictionary ViewData
+ {
+ get { return ControllerContext.Controller.ViewData; }
+ }
+
+ public ModelStateDictionary ModelState
+ {
+ get { return ControllerContext.Controller.ViewData.ModelState; }
+ }
+
+ public HttpResponseBase Response
+ {
+ get { return ControllerContext.HttpContext.Response; }
+ }
+
+ public ControllerBase Controller
+ {
+ get { return ControllerContext.Controller; }
+ }
+
+ public HttpRequestBase Request
+ {
+ get { return ControllerContext.HttpContext.Request; }
+ }
+
+ public void ExecuteResult()
+ {
+ ActionResult.ExecuteResult(ControllerContext);
+ }
+ }
+}
diff --git a/TestStack.FluentMvcTesting/MvcPipeline/HttpContextSimulator.cs b/TestStack.FluentMvcTesting/MvcPipeline/HttpContextSimulator.cs
new file mode 100644
index 0000000..4d28ff6
--- /dev/null
+++ b/TestStack.FluentMvcTesting/MvcPipeline/HttpContextSimulator.cs
@@ -0,0 +1,34 @@
+using System.Web;
+
+namespace TestStack.FluentMVCTesting.MvcPipeline
+{
+ internal class HttpContextSimulator : HttpContextWrapper
+ {
+ private readonly HttpRequestSimulator _request;
+ private readonly HttpResponseWrapper _response;
+ private readonly SimpleSessionState _session;
+
+ public HttpContextSimulator(HttpContext httpContext)
+ : base(httpContext)
+ {
+ _request = new HttpRequestSimulator(httpContext.Request);
+ _response = new HttpResponseWrapper(httpContext.Response);
+ _session = new SimpleSessionState();
+ }
+
+ public override HttpRequestBase Request
+ {
+ get { return _request; }
+ }
+
+ public override HttpResponseBase Response
+ {
+ get { return _response; }
+ }
+
+ public override HttpSessionStateBase Session
+ {
+ get { return _session; }
+ }
+ }
+}
diff --git a/TestStack.FluentMvcTesting/MvcPipeline/HttpRequestSimulator.cs b/TestStack.FluentMvcTesting/MvcPipeline/HttpRequestSimulator.cs
new file mode 100644
index 0000000..a1ffc2f
--- /dev/null
+++ b/TestStack.FluentMvcTesting/MvcPipeline/HttpRequestSimulator.cs
@@ -0,0 +1,17 @@
+using System.Web;
+
+namespace TestStack.FluentMVCTesting.MvcPipeline
+{
+ internal class HttpRequestSimulator : HttpRequestWrapper
+ {
+ public HttpRequestSimulator(HttpRequest httpRequest)
+ : base(httpRequest)
+ {
+ }
+
+ public override string AppRelativeCurrentExecutionFilePath
+ {
+ get { return "~" + this.Url.AbsolutePath; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/TestStack.FluentMvcTesting/MvcPipeline/MvcActionDescriptor.cs b/TestStack.FluentMvcTesting/MvcPipeline/MvcActionDescriptor.cs
new file mode 100644
index 0000000..150b972
--- /dev/null
+++ b/TestStack.FluentMvcTesting/MvcPipeline/MvcActionDescriptor.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+using System.Web.Mvc;
+
+namespace TestStack.FluentMVCTesting.MvcPipeline
+{
+ internal class MvcActionDescriptor : ReflectedActionDescriptor
+ {
+ private MvcActionDescriptor(MethodCallExpression methodCallExpression, ReflectedControllerDescriptor controllerDescriptor)
+ : base(methodCallExpression.Method, methodCallExpression.Method.Name, controllerDescriptor)
+ {
+ }
+
+ public static ActionDescriptor Create(Expression> actionExpression)
+ where TController : ControllerBase
+ where TActionResult : ActionResult
+ {
+ var methodCallExpression = Reflector.GetMethodCallExpression(actionExpression.Body);
+
+ return new MvcActionDescriptor(methodCallExpression, new ReflectedControllerDescriptor(typeof(TController)));
+ }
+
+ public static ActionDescriptor Create(Expression>> actionExpression)
+ where TController : ControllerBase
+ where TActionResult : ActionResult
+ {
+ var methodCallExpression = Reflector.GetMethodCallExpression(actionExpression.Body);
+
+ return new MvcActionDescriptor(methodCallExpression, new ReflectedControllerDescriptor(typeof(TController)));
+ }
+ }
+}
diff --git a/TestStack.FluentMvcTesting/MvcPipeline/MvcActionInvoker.cs b/TestStack.FluentMvcTesting/MvcPipeline/MvcActionInvoker.cs
new file mode 100644
index 0000000..0aa61f1
--- /dev/null
+++ b/TestStack.FluentMvcTesting/MvcPipeline/MvcActionInvoker.cs
@@ -0,0 +1,116 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Web.Mvc;
+
+namespace TestStack.FluentMVCTesting.MvcPipeline
+{
+ internal class MvcActionInvoker : ControllerActionInvoker
+ {
+ private readonly ControllerContext _controllerContext;
+ private readonly ActionDescriptor _actionDescriptor;
+ private readonly FilterInfo _filterInfo;
+
+ public MvcActionInvoker(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IEnumerable filters)
+ {
+ _controllerContext = controllerContext;
+ _actionDescriptor = actionDescriptor;
+ _filterInfo = new FilterInfo(filters);
+ }
+
+ public virtual ActionResult AuthorizeAction()
+ {
+ var authorizationContext = InvokeAuthorizationFilters(_controllerContext,
+ _filterInfo.AuthorizationFilters, _actionDescriptor);
+
+ return authorizationContext.Result;
+ }
+
+ public virtual ActionResult InvokeAction()
+ {
+ return AuthorizeAction() ?? InvokeActionMethodWithFilters();
+ }
+
+ private ActionResult InvokeActionMethodWithFilters()
+ {
+ var controller = _controllerContext.Controller as Controller;
+ if (controller != null)
+ {
+ // controller.ViewEngineCollection = new ViewEngineCollection(new IViewEngine[] { new RazorViewEngineSimulator() });
+ }
+
+ var parameters = GetParameterValues(_controllerContext, _actionDescriptor);
+ var actionExecutedContext = InvokeActionMethodWithFilters(_controllerContext,
+ _filterInfo.ActionFilters, _actionDescriptor, parameters);
+
+ if (actionExecutedContext == null)
+ throw new Exception("InvokeActionMethodWithFilters returned null");
+
+ return actionExecutedContext.Result;
+ }
+
+ protected override object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
+ {
+ var parameterName = parameterDescriptor.ParameterName;
+ var value = base.GetParameterValue(controllerContext, parameterDescriptor);
+
+ if (value == null)
+ return null;
+
+ var validationResults = ValidateModel(parameterDescriptor.ParameterType, value, controllerContext);
+
+ var modelState = controllerContext.Controller.ViewData.ModelState;
+ Func isValidField = res => modelState.IsValidField(String.Format("{0}.{1}", parameterName, res.MemberName));
+
+ foreach (var validationResult in validationResults.Where(isValidField).ToArray())
+ {
+ var subPropertyName = String.Format("{0}.{1}", parameterDescriptor.ParameterName, validationResult.MemberName);
+ modelState.AddModelError(subPropertyName, validationResult.Message);
+ }
+
+ return value;
+ }
+
+ protected virtual IEnumerable ValidateModel(Type modelType, object modelValue, ControllerContext controllerContext)
+ {
+ var modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => modelValue, modelType);
+ return ModelValidator.GetModelValidator(modelMetadata, controllerContext).Validate(null);
+ }
+ }
+ internal class RazorViewEngineSimulator : IViewEngine
+ {
+ public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
+ {
+ throw new NotImplementedException();
+ }
+
+ public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
+ {
+ return new ViewEngineResult(new ViewSimulator(viewName), this);
+ }
+
+ public void ReleaseView(ControllerContext controllerContext, IView view)
+ {
+ }
+ }
+
+ internal class ViewSimulator : IView
+ {
+ private readonly string _viewName;
+
+ public ViewSimulator(string viewName)
+ {
+ _viewName = viewName;
+ }
+
+ public void Render(ViewContext viewContext, TextWriter writer)
+ {
+ var model = viewContext.ViewData.Model;
+ var html = "hello @Model
";
+
+ writer.Write(html);
+ }
+ }
+
+}
diff --git a/TestStack.FluentMvcTesting/MvcPipeline/MvcActionValueProvider.cs b/TestStack.FluentMvcTesting/MvcPipeline/MvcActionValueProvider.cs
new file mode 100644
index 0000000..808a008
--- /dev/null
+++ b/TestStack.FluentMvcTesting/MvcPipeline/MvcActionValueProvider.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq.Expressions;
+using System.Web.Mvc;
+
+namespace TestStack.FluentMVCTesting.MvcPipeline
+{
+ public class MvcActionValueProvider : IValueProvider
+ {
+ private readonly Dictionary _values;
+
+ public MvcActionValueProvider(Expression body)
+ {
+ _values = new Dictionary();
+
+ var methodCallExpression = (MethodCallExpression)body;
+ var methodParameters = methodCallExpression.Method.GetParameters();
+ for (int i = 0; i < methodCallExpression.Arguments.Count; i++)
+ {
+ var arg = methodCallExpression.Arguments[i];
+ var par = methodParameters[i];
+ _values.Add(par.Name, Invoke(arg));
+ }
+ }
+ private static object Invoke(Expression valueExpression)
+ {
+ var convertExpression = Expression.Convert(valueExpression, typeof(object));
+ var express = Expression.Lambda>(convertExpression).Compile();
+ return express.Invoke();
+ }
+ public bool ContainsPrefix(string prefix)
+ {
+ return _values.ContainsKey(prefix);
+ }
+
+ public ValueProviderResult GetValue(string key)
+ {
+ return new ValueProviderResult(_values[key], null, CultureInfo.InvariantCulture);
+ }
+ }
+}
diff --git a/TestStack.FluentMvcTesting/MvcPipeline/MvcControllerAction.cs b/TestStack.FluentMvcTesting/MvcPipeline/MvcControllerAction.cs
new file mode 100644
index 0000000..5e0e777
--- /dev/null
+++ b/TestStack.FluentMvcTesting/MvcPipeline/MvcControllerAction.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Linq;
+using System.Web;
+using System.Web.Mvc;
+using System.Web.Routing;
+
+namespace TestStack.FluentMVCTesting.MvcPipeline
+{
+ public class MvcControllerAction : ControllerActionBase
+ {
+ public MvcControllerAction(ControllerBase controller, System.Web.Mvc.ActionDescriptor actionDescriptor, RouteCollection routes)
+ {
+ Controller = controller;
+ ActionDescriptor = actionDescriptor;
+ Routes = routes;
+ }
+
+ public virtual RouteCollection Routes { get; private set; }
+
+ public virtual ControllerBase Controller { get; private set; }
+
+ public virtual System.Web.Mvc.ActionDescriptor ActionDescriptor { get; private set; }
+
+ public override ActionContext GetActionContext(HttpContextBase httpContext = null)
+ {
+ InitializeController(httpContext);
+
+ return new ActionContext
+ {
+ ControllerContext = Controller.ControllerContext,
+ ActionDescriptor = ActionDescriptor
+ };
+ }
+
+ protected virtual void InitializeController(HttpContextBase httpContext)
+ {
+ var controllerContext = CreateControllerContext(httpContext ?? CreateHttpContext(), Controller,
+ ActionDescriptor);
+
+ // Use empty value provider by default to prevent use of ASP.NET MVC default value providers
+ // Either a value provider is not needed or a custom implementation is provided.
+ Controller.ValueProvider = ValueProvider ?? new ValueProviderCollection();
+ Controller.ControllerContext = controllerContext;
+ var controller = Controller as Controller;
+ if (controller != null)
+ {
+ controller.Url = new UrlHelper(controllerContext.RequestContext, Routes);
+ }
+ }
+
+ public override HttpContextBase CreateHttpContext()
+ {
+ return CreateHttpContext(this, ActionDescriptor);
+ }
+
+ public virtual ControllerContext CreateControllerContext(HttpContextBase httpContext, ControllerBase controller, System.Web.Mvc.ActionDescriptor actionDescriptor)
+ {
+ var requestContext = GetRequestContext(httpContext, actionDescriptor);
+ var controllerContext = new ControllerContext(requestContext, controller);
+
+ if (actionDescriptor.GetSelectors().Any(selector => !selector.Invoke(controllerContext)))
+ {
+ throw new ControllerActionException(String.Format("Http method '{0}' is not allowed", controllerContext.HttpContext.Request.HttpMethod));
+ }
+
+ return controllerContext;
+ }
+
+ public HttpContextBase CreateHttpContext(MvcControllerAction actionRequest, System.Web.Mvc.ActionDescriptor actionDescriptor)
+ {
+ var controllerDescriptor = actionDescriptor.ControllerDescriptor;
+ var controllerName = controllerDescriptor.ControllerName;
+
+ var user = actionRequest.User ?? WebHelpers.CreateAnonymousUser();
+ var httpContext =
+ WebHelpers.GetContext(String.Format("/{0}/{1}", controllerName, actionDescriptor.ActionName),
+ actionRequest.HttpMethod, user);
+ return httpContext;
+ }
+
+ protected virtual RequestContext GetRequestContext(HttpContextBase httpContext, System.Web.Mvc.ActionDescriptor actionDescriptor)
+ {
+ var requestContext = WebHelpers.CreateRequestContext(httpContext,
+ actionDescriptor.ControllerDescriptor.ControllerName,
+ actionDescriptor.ActionName);
+
+ foreach (var cookie in Cookies)
+ requestContext.HttpContext.Request.Cookies.Add(cookie);
+
+ foreach (var kvp in Session)
+ // ReSharper disable once PossibleNullReferenceException
+ requestContext.HttpContext.Session[kvp.Key] = kvp.Value;
+ return requestContext;
+ }
+ }
+}
\ No newline at end of file
diff --git a/TestStack.FluentMvcTesting/MvcPipeline/PipelineActionExecutor.cs b/TestStack.FluentMvcTesting/MvcPipeline/PipelineActionExecutor.cs
new file mode 100644
index 0000000..eb8c700
--- /dev/null
+++ b/TestStack.FluentMvcTesting/MvcPipeline/PipelineActionExecutor.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+using System.Web.Mvc;
+using System.Web.Routing;
+
+namespace TestStack.FluentMVCTesting.MvcPipeline
+{
+ public class PipelineActionExecutor : IActionExecutor
+ {
+ public ControllerResultTest Execute(TController controller, Expression> actionCall)
+ where TController : Controller
+ where TAction : ActionResult
+ {
+ var routes = GetDefaultRoutes();
+ var action = new MvcControllerAction(controller, MvcActionDescriptor.Create(actionCall), routes)
+ {
+ ValueProvider = new MvcActionValueProvider(actionCall.Body),
+ HttpMethod = "Get"
+ };
+ var httpContext = action.CreateHttpContext();
+ var result = action.Execute(httpContext);
+ return new ControllerResultTest((TController)result.Controller, action.ActionDescriptor.ActionName, result.ActionResult);
+ }
+
+ public ControllerResultTest Execute(TController controller, Expression>> actionCall) where TController : Controller where TAction : ActionResult
+ {
+ var routes = GetDefaultRoutes();
+ var action = new MvcControllerAction(controller, MvcActionDescriptor.Create(actionCall), routes)
+ {
+ ValueProvider = new MvcActionValueProvider(actionCall.Body),
+ HttpMethod = "Get"
+ };
+ var httpContext = action.CreateHttpContext();
+ var result = action.Execute(httpContext);
+ return new ControllerResultTest((TController)result.Controller, action.ActionDescriptor.ActionName, result.ActionResult);
+ }
+
+ private static RouteCollection GetDefaultRoutes()
+ {
+ var routes = new RouteCollection();
+ if (RouteTable.Routes.Count == 0)
+ {
+ routes.MapRoute(
+ "Default",
+ "{controller}/{action}/{id}",
+ new { controller = "Home", action = "Index", id = UrlParameter.Optional }
+ );
+ }
+ else
+ {
+ foreach (var route in RouteTable.Routes)
+ routes.Add(route);
+ }
+
+ return routes;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/TestStack.FluentMvcTesting/MvcPipeline/Reflector.cs b/TestStack.FluentMvcTesting/MvcPipeline/Reflector.cs
new file mode 100644
index 0000000..c4e8301
--- /dev/null
+++ b/TestStack.FluentMvcTesting/MvcPipeline/Reflector.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Linq.Expressions;
+
+namespace TestStack.FluentMVCTesting.MvcPipeline
+{
+ public static class Reflector
+ {
+ public static MethodCallExpression GetMethodCallExpression(Expression actionBody)
+ {
+ var methodCallExpression = actionBody as MethodCallExpression;
+ if (methodCallExpression != null)
+ return methodCallExpression;
+
+ if (!(actionBody is UnaryExpression))
+ throw new InvalidOperationException("Unable to reduce expression to MethodCallExpression");
+
+ var unaryExpr = actionBody as UnaryExpression;
+ if (unaryExpr.NodeType == ExpressionType.Convert)
+ return GetMethodCallExpression(unaryExpr.Operand);
+
+ throw new InvalidOperationException("Unable to reduce expression to MethodCallExpression");
+ }
+ }
+}
\ No newline at end of file
diff --git a/TestStack.FluentMvcTesting/MvcPipeline/SimpleHttpRequest.cs b/TestStack.FluentMvcTesting/MvcPipeline/SimpleHttpRequest.cs
new file mode 100644
index 0000000..d3fbe72
--- /dev/null
+++ b/TestStack.FluentMvcTesting/MvcPipeline/SimpleHttpRequest.cs
@@ -0,0 +1,11 @@
+using System.Security.Principal;
+
+namespace TestStack.FluentMVCTesting.MvcPipeline
+{
+ internal class SimpleHttpRequest
+ {
+ public string UriPath { get; set; }
+ public string HttpMethod { get; set; }
+ public IPrincipal User { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TestStack.FluentMvcTesting/MvcPipeline/SimpleSessionState.cs b/TestStack.FluentMvcTesting/MvcPipeline/SimpleSessionState.cs
new file mode 100644
index 0000000..ac59cb7
--- /dev/null
+++ b/TestStack.FluentMvcTesting/MvcPipeline/SimpleSessionState.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using System.Web;
+
+namespace TestStack.FluentMVCTesting.MvcPipeline
+{
+ internal class SimpleSessionState : HttpSessionStateBase
+ {
+ private readonly IDictionary _values;
+
+ public SimpleSessionState()
+ {
+ this._values = new Dictionary();
+ }
+
+ public override void Add(string name, object value)
+ {
+ this._values.Add(name, value);
+ }
+
+ public override object this[string name]
+ {
+ get { return this._values[name]; }
+ set { this._values[name] = value; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/TestStack.FluentMvcTesting/MvcPipeline/WebHelpers.cs b/TestStack.FluentMvcTesting/MvcPipeline/WebHelpers.cs
new file mode 100644
index 0000000..7189984
--- /dev/null
+++ b/TestStack.FluentMvcTesting/MvcPipeline/WebHelpers.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Security.Principal;
+using System.Web;
+using System.Web.Routing;
+
+namespace TestStack.FluentMVCTesting.MvcPipeline
+{
+ public class WebHelpers
+ {
+ internal static RequestContext CreateRequestContext(string actionName, string controllerName, string httpMethod, IPrincipal user)
+ {
+ var httpContext = GetContext(String.Format("/{0}/{1}", controllerName, actionName), httpMethod, user);
+ return CreateRequestContext(httpContext, controllerName, actionName);
+ }
+
+ internal static RequestContext CreateRequestContext(HttpContextBase httpContext, string controllerName, string actionName)
+ {
+ var routeData = new RouteData { Values = { { "controller", controllerName }, { "action", actionName } } };
+ return new RequestContext(httpContext, routeData);
+ }
+
+ internal static HttpContextBase GetContext(string url, string method, IPrincipal user)
+ {
+ return GetContext(new SimpleHttpRequest
+ {
+ UriPath = url,
+ User = user,
+ HttpMethod = method
+ });
+ }
+
+ internal static HttpContextBase GetContext(SimpleHttpRequest httpRequest)
+ {
+ var worker = new ActionRequestWrapper(httpRequest);
+ var httpContext = new HttpContext(worker)
+ {
+ User = httpRequest.User ?? CreateAnonymousUser(),
+ };
+
+ return new HttpContextSimulator(httpContext);
+ }
+
+ public static IPrincipal CreateAnonymousUser()
+ {
+ return new GenericPrincipal(new GenericIdentity(String.Empty), new string[] { });
+ }
+
+ }
+}
diff --git a/TestStack.FluentMvcTesting/SimpleActionExecutor.cs b/TestStack.FluentMvcTesting/SimpleActionExecutor.cs
new file mode 100644
index 0000000..6543886
--- /dev/null
+++ b/TestStack.FluentMvcTesting/SimpleActionExecutor.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+using System.Web.Mvc;
+
+namespace TestStack.FluentMVCTesting
+{
+ public class SimpleActionExecutor : IActionExecutor
+ {
+ public ControllerResultTest Execute(TController controller, Expression> actionCall)
+ where TController : Controller
+ where TAction : ActionResult
+ {
+ var actionName = ((MethodCallExpression)actionCall.Body).Method.Name;
+
+ var actionResult = actionCall.Compile().Invoke(controller);
+
+ return new ControllerResultTest(controller, actionName, actionResult);
+ }
+
+ public ControllerResultTest Execute(TController controller, Expression>> actionCall)
+ where TController : Controller
+ where TAction : ActionResult
+ {
+ var actionName = ((MethodCallExpression)actionCall.Body).Method.Name;
+
+ var actionResult = actionCall.Compile().Invoke(controller).Result;
+
+ return new ControllerResultTest(controller, actionName, actionResult);
+ }
+ }
+}
\ No newline at end of file
diff --git a/TestStack.FluentMvcTesting/TestStack.FluentMVCTesting.csproj b/TestStack.FluentMvcTesting/TestStack.FluentMVCTesting.csproj
index b9925ec..d3a145a 100644
--- a/TestStack.FluentMvcTesting/TestStack.FluentMVCTesting.csproj
+++ b/TestStack.FluentMvcTesting/TestStack.FluentMVCTesting.csproj
@@ -84,6 +84,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -101,6 +118,7 @@
+