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 @@ +