Skip to content

Instantly share code, notes, and snippets.

@ArnisL
Created January 3, 2012 17:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ArnisL/1555885 to your computer and use it in GitHub Desktop.
Save ArnisL/1555885 to your computer and use it in GitHub Desktop.
Asp.net mvc route testing
namespace Interreg.Integration.Web.Routing{
using Extensions;
using Interreg.Web.Controllers;
using Xunit;
public class ApplicationsFacts:Base{
[Fact]
public void Application(){
"~/Applications/1"
.ShouldMapTo<ApplicationsController>(c=>c.Application(null,null));
}
[Fact]
public void ApplicationWithTab(){
"~/Applications/1/Budget"
.ShouldMapTo<ApplicationsController>(c=>c.Application(null,"Budget"));
}
[Fact]
public void Register(){
"~/Applications/Register"
.ShouldMapTo<ApplicationsController>(c=>c.Register());
}
[Fact]
public void Download(){
"~/Applications/1/Download"
.ShouldMapTo<ApplicationsController>(c=>c.Download(null));
}
[Fact]
public void Remove(){
"~/Applications/1/Remove"
.ShouldMapTo<ApplicationsController>(c=>c.Remove(null));
}
[Fact]
public void Update(){
"~/Applications/1/Update"
.ShouldMapTo<ApplicationsController>(c=>c.Update(null));
}
[Fact]
public void Withdraw(){
"~/Applications/1/Withdraw"
.ShouldMapTo<ApplicationsController>(c=>c.Withdraw(null));
}
}
}
namespace Interreg.Integration.Web.Routing{
using System.Web.Routing;
using Interreg.Web.Cfg.Tasks;
public class Base{
public Base(){
RouteTable.Routes.Clear();
new RoutingTask().Execute();
}
}
}
namespace Interreg.Integration.Web.Fakes{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Security.Principal;
using System.Web;
using System.Web.SessionState;
public class FakeHttpContext:HttpContextBase{
private readonly HttpCookieCollection _cookies;
private readonly NameValueCollection _formParams;
private readonly Dictionary<object,object> _items;
private readonly string _method;
private readonly NameValueCollection _queryStringParams;
private readonly string _relativeUrl;
private readonly SessionStateItemCollection _sessionItems;
private IPrincipal _principal;
private HttpRequestBase _request;
private HttpResponseBase _response;
public FakeHttpContext(string relativeUrl,string method)
:this(relativeUrl,method,null,null,null,null,null) {}
public FakeHttpContext(string relativeUrl)
:this(relativeUrl,null,null,null,null,null) {}
public FakeHttpContext(string relativeUrl,IPrincipal principal,NameValueCollection formParams,
NameValueCollection queryStringParams,HttpCookieCollection cookies,
SessionStateItemCollection sessionItems)
:this(relativeUrl,null,principal,formParams,queryStringParams,cookies,sessionItems) {}
public FakeHttpContext(string relativeUrl,string method,IPrincipal principal,NameValueCollection formParams,
NameValueCollection queryStringParams,HttpCookieCollection cookies,
SessionStateItemCollection sessionItems){
_relativeUrl=relativeUrl;
_method=method;
_principal=principal;
_formParams=formParams;
_queryStringParams=queryStringParams;
_cookies=cookies;
_sessionItems=sessionItems;
_items=new Dictionary<object,object>();
}
public override HttpRequestBase Request{
get{
return _request??
new FakeHttpRequest(_relativeUrl,_method,_formParams,_queryStringParams,_cookies);
}
}
public override HttpResponseBase Response{
get {return _response??new FakeHttpResponse();}
}
public override IPrincipal User{
get {return _principal;}
set {_principal=value;}
}
public override HttpSessionStateBase Session{
get {return new FakeHttpSessionState(_sessionItems);}
}
public override IDictionary Items{
get {return _items;}
}
public override bool SkipAuthorization{get;set;}
public static FakeHttpContext Root(){
return new FakeHttpContext("~/");
}
public void SetRequest(HttpRequestBase request){
_request=request;
}
public void SetResponse(HttpResponseBase response){
_response=response;
}
public override object GetService(Type serviceType){
return null;
}
}
}
namespace Interreg.Integration.Web.Fakes{
using System;
using System.Collections.Specialized;
using System.Web;
using System.Web.Mvc;
public class FakeHttpRequest:HttpRequestBase{
private readonly HttpCookieCollection _cookies;
private readonly NameValueCollection _formParams;
private readonly string _httpMethod;
private readonly NameValueCollection _queryStringParams;
private readonly string _relativeUrl;
private readonly NameValueCollection _serverVariables;
private readonly Uri _url;
private readonly Uri _urlReferrer;
public FakeHttpRequest(string relativeUrl,string method,NameValueCollection formParams,
NameValueCollection queryStringParams,
HttpCookieCollection cookies){
_httpMethod=method;
_relativeUrl=relativeUrl;
_formParams=formParams;
_queryStringParams=queryStringParams;
_cookies=cookies;
_serverVariables=new NameValueCollection();
}
public FakeHttpRequest(string relativeUrl,string method,Uri url,Uri urlReferrer,NameValueCollection formParams,
NameValueCollection queryStringParams,
HttpCookieCollection cookies)
:this(relativeUrl,method,formParams,queryStringParams,cookies){
_url=url;
_urlReferrer=urlReferrer;
}
public FakeHttpRequest(string relativeUrl,Uri url,Uri urlReferrer)
:this(relativeUrl,HttpVerbs.Get.ToString("g"),url,urlReferrer,null,null,null) {}
public override NameValueCollection ServerVariables{
get {return _serverVariables;}
}
public override NameValueCollection Form{
get {return _formParams;}
}
public override NameValueCollection QueryString{
get {return _queryStringParams;}
}
public override HttpCookieCollection Cookies{
get {return _cookies;}
}
public override string AppRelativeCurrentExecutionFilePath{
get {return _relativeUrl;}
}
public override Uri Url{
get {return _url;}
}
public override Uri UrlReferrer{
get {return _urlReferrer;}
}
public override string PathInfo{
get {return String.Empty;}
}
public override string ApplicationPath{
get {return "";}
}
public override string HttpMethod{
get {return _httpMethod;}
}
}
}
namespace Interreg.Integration.Web.Fakes{
using System.Text;
using System.Web;
public class FakeHttpResponse:HttpResponseBase{
private readonly StringBuilder _outputString=new StringBuilder();
public string ResponseOutput{
get {return _outputString.ToString();}
}
public override int StatusCode{get;set;}
public override string RedirectLocation{get;set;}
public override void Write(string s){
_outputString.Append(s);
}
public override string ApplyAppPathModifier(string virtualPath){
return virtualPath;
}
}
}
namespace Interreg.Integration.Web.Fakes{
using System.Collections;
using System.Collections.Specialized;
using System.Web;
using System.Web.SessionState;
public class FakeHttpSessionState:HttpSessionStateBase{
private readonly SessionStateItemCollection _sessionItems;
public FakeHttpSessionState(SessionStateItemCollection sessionItems){
_sessionItems=sessionItems;
}
public override int Count{
get {return _sessionItems.Count;}
}
public override NameObjectCollectionBase.KeysCollection Keys{
get {return _sessionItems.Keys;}
}
public override object this[string name]{
get {return _sessionItems[name];}
set {_sessionItems[name]=value;}
}
public override object this[int index]{
get {return _sessionItems[index];}
set {_sessionItems[index]=value;}
}
public bool Exists(string key){
return _sessionItems[key]!=null;
}
public override void Add(string name,object value){
_sessionItems[name]=value;
}
public override IEnumerator GetEnumerator(){
return _sessionItems.GetEnumerator();
}
public override void Remove(string name){
_sessionItems.Remove(name);
}
}
}
namespace Interreg.Integration.Web.Fakes{
using System;
using System.Security.Principal;
public class FakeIdentity:IIdentity{
private readonly string _name;
public FakeIdentity(string userName){
_name=userName;
}
public string AuthenticationType{
get {throw new NotImplementedException();}
}
public bool IsAuthenticated{
get {return !String.IsNullOrEmpty(_name);}
}
public string Name{
get {return _name;}
}
}
}
namespace Interreg.Integration.Web.Fakes{
using System.Linq;
using System.Security.Principal;
public class FakePrincipal:IPrincipal{
private readonly IIdentity _identity;
private readonly string[] _roles;
public FakePrincipal(IIdentity identity,string[] roles){
_identity=identity;
_roles=roles;
}
public IIdentity Identity{
get {return _identity;}
}
public bool IsInRole(string role){
return _roles!=null&&_roles.Contains(role);
}
}
}
namespace Interreg.Integration.Web.Routing{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web.Mvc;
using Xunit;
using ControllerBase=Interreg.Web.Controllers.Base;
public class MetaFacts{
private readonly StringBuilder _sb=new StringBuilder();
private Type _controller;
private IEnumerable<Type> _controllerClasses;
private Type _factClass;
private IEnumerable<Type> _factsClasses;
private bool pairFound;
[Fact]
public void AllRoutesAreTested(){
_controllerClasses=GetControllerTypes();
_factsClasses=GetRoutingFactTypes();
_controllerClasses.ForEach(TestThatControllerIsTested);
if(_sb.Length>0) Assert.False(true,Environment.NewLine+_sb);
}
private void TestThatControllerIsTested(Type ct){
_controller=ct;
pairFound=false;
_factsClasses.ForEach(TestFactClassAgainstController);
if(!pairFound)
_sb.AppendLine("No routing facts for '{0}'".With(_controller.Name));
}
private void TestFactClassAgainstController(Type rt){
_factClass=rt;
if(!PairFound()) return;
pairFound=true;
VerifyAllActionsAreTested();
}
private void VerifyAllActionsAreTested(){
var actions=GetActions();
actions.ForEach(VerifyActionIsTested);
}
private void VerifyActionIsTested(string action){
var facts=_factClass.GetMethods().Where(IsMarkedAsFact);
if(!facts.Any(f=>f.Name==action))
_sb.AppendLine("No routing facts for '{0}#{1}' action"
.With(_controller.Name,action));
}
#region boring
private static bool IsMarkedAsFact(MethodInfo mi){
return mi.GetCustomAttributes(false).Any(a=>a.GetType().IsAssignableFrom(typeof(FactAttribute)));
}
private IEnumerable<string> GetActions(){
return _controller.GetMethods()
.Where(IsAction)
.Where(IsNotBlacklistedAction)
.Select(x=>x.Name);
}
private IEnumerable<Type> GetRoutingFactTypes(){
return GetType().Assembly.GetTypes().Where(IsRoutingFacts);
}
private static IEnumerable<Type> GetControllerTypes(){
return typeof(ControllerBase).Assembly.GetTypes().Where(IsController);
}
private static bool IsAction(MethodInfo mi){
return mi.ReturnType.IsAssignableFrom(typeof(ActionResult))||
mi.ReturnType.IsAssignableFrom(typeof(PartialViewResult));
}
private static bool IsNotBlacklistedAction(MethodInfo mi){
return !mi.Name.IsOneOf("Do","View","get_ViewBag","PartialView");
}
private static bool IsController(Type t){
return t.BaseType==typeof(ControllerBase);
}
private static bool IsRoutingFacts(Type t){
return t.BaseType==typeof(Base);
}
private bool PairFound(){
return _controller.Name.Erase("Controller")==_factClass.Name.Erase("Facts");
}
#endregion
}
}
//shamelessly stolen from MvcContrib: http://bit.ly/dQfmT7
namespace Interreg.Integration.Web.Extensions{
using System;
using System.Collections.Specialized;
using System.Linq.Expressions;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Fakes;
using Interreg.Domain;
using Interreg.Domain.Model;
using Mocks;
using SharpTestsEx;
using Xunit;
///<summary>
/// Used to simplify testing routes and restful testing routes
/// <example>
/// This tests that incoming PUT on resource is redirected to Update
/// "~/banner/1"
/// .WithMethod(HttpVerbs.Put)
/// .ShouldMapTo&lt;BannerController>(action => action.Update(1));
///
/// This tests that incoming POST was a faux PUT using the _method=PUT form parameter
/// "~/banner/1"
/// .WithMethod(HttpVerbs.Post, HttpVerbs.Put)
/// .ShouldMapTo&lt;BannerController>(action => action.Update(1));
/// </example>
///</summary>
public static class RouteTestingExtensions{
/// <summary>
/// A way to start the fluent interface and and which method to use
/// since you have a method constraint in the route.
/// </summary>
/// <param name = "url"></param>
/// <param name = "httpMethod"></param>
/// <returns></returns>
public static RouteData WithMethod(this string url,string httpMethod){
return Route(url,httpMethod);
}
public static RouteData WithMethod(this string url,HttpVerbs verb){
return WithMethod(url,verb.ToString("g"));
}
/// <summary>
/// Find the route for a URL and an Http Method
/// because you have a method contraint on the route
/// </summary>
/// <param name = "url"></param>
/// <param name = "httpMethod"></param>
/// <returns></returns>
public static RouteData Route(string url,string httpMethod){
var context=FakeHttpContext(url,httpMethod);
return RouteTable.Routes.GetRouteData(context);
}
/// <summary>
/// Returns the corresponding route for the URL. Returns null if no route was found.
/// </summary>
/// <param name = "url">The app relative url to test.</param>
/// <returns>A matching <see cref = "RouteData" />, or null.</returns>
public static RouteData Route(this string url){
var context=FakeHttpContext(url);
return RouteTable.Routes.GetRouteData(context);
}
/// <summary>
/// Returns the corresponding route for the URL. Returns null if no route was found.
/// </summary>
/// <param name = "url">The URL.</param>
/// <param name = "httpMethod">The HTTP method.</param>
/// <param name = "formMethod">The form method.</param>
/// <returns></returns>
public static RouteData Route(this string url,HttpVerbs httpMethod,HttpVerbs formMethod){
var context=FakeHttpContext(url,httpMethod,formMethod);
var route=RouteTable.Routes.GetRouteData(context);
// cater for SimplyRestful methods and others
// adding values during the GetHttpHandler method
route.ReadValue(x=>x.RouteHandler).ReadValue(x=>x.GetHttpHandler(new RequestContext(context,route)));
return route;
}
/// <summary>
/// Returns the corresponding route for the URL. Returns null if no route was found.
/// </summary>
/// <param name = "url">The URL.</param>
/// <param name = "httpMethod">The HTTP method.</param>
/// <returns></returns>
public static RouteData Route(this string url,HttpVerbs httpMethod){
var context=FakeHttpContext(url,httpMethod);
var route=RouteTable.Routes.GetRouteData(context);
// cater for SimplyRestful methods and others
// adding values during the GetHttpHandler method
route.ReadValue(x=>x.RouteHandler).ReadValue(x=>x.GetHttpHandler(new RequestContext(context,route)));
return route;
}
/// <summary>
/// Asserts that the route matches the expression specified based on the incoming HttpMethod and FormMethod for Simply Restful routing. Checks controller, action, and any method arguments
/// into the action as route values.
/// </summary>
/// <param name = "relativeUrl">The relative URL.</param>
/// <param name = "httpMethod">The HTTP method.</param>
/// <param name = "formMethod">The form method.</param>
/// <returns></returns>
public static RouteData WithMethod(this string relativeUrl,HttpVerbs httpMethod,HttpVerbs formMethod){
return relativeUrl.Route(httpMethod,formMethod);
}
private static HttpContextBase FakeHttpContext(string url,HttpVerbs? httpMethod,HttpVerbs? formMethod){
NameValueCollection form=null;
if(formMethod.HasValue)
form=new NameValueCollection {{"_method",formMethod.Value.ToString().ToUpper()}};
if(!httpMethod.HasValue)
httpMethod=HttpVerbs.Get;
var mockFactory=new FirstAvailableMockFactory();
var request=mockFactory.DynamicMock<HttpRequestBase>();
request.ReturnFor(x=>x.AppRelativeCurrentExecutionFilePath,url);
request.ReturnFor(x=>x.PathInfo,string.Empty);
request.ReturnFor(x=>x.Form,form);
request.ReturnFor(x=>x.HttpMethod,httpMethod.Value.ToString().ToUpper());
var context=new FakeHttpContext(url);
context.SetRequest(request.Object);
return context;
}
private static HttpContextBase FakeHttpContext(string url,string method){
var httpMethod=(HttpVerbs)Enum.Parse(typeof(HttpVerbs),method);
return FakeHttpContext(url,httpMethod,null);
}
private static HttpContextBase FakeHttpContext(string url,HttpVerbs? httpMethod){
return FakeHttpContext(url,httpMethod,null);
}
private static HttpContextBase FakeHttpContext(string url){
return FakeHttpContext(url,null,null);
}
/// <summary>
/// Asserts that the route matches the expression specified. Checks controller, action, and any method arguments
/// into the action as route values.
/// </summary>
/// <typeparam name = "TController">The controller.</typeparam>
/// <param name = "routeData">The routeData to check</param>
/// <param name = "action">The action to call on TController.</param>
public static RouteData ShouldMapTo<TController>(this RouteData routeData,
Expression<Func<TController,ActionResult>> action)
where TController:Controller{
if (routeData == null)
Assert.False(true,"The URL did not match any route");
//routeData.Should().Not.Be.Null();
//routeData.Should().Not.Be.Null("The URL did not match any route");
//check controller
routeData.ShouldMapTo<TController>();
//check action
var methodCall=(MethodCallExpression)action.Body;
var actualAction=routeData.Values.GetValue("action").ToString();
var expectedAction=methodCall.Method.ActionName();
//actualAction.AssertSameStringAs(expectedAction);
actualAction.Should().Be(expectedAction);
//check parameters
for(var i=0;i<methodCall.Arguments.Count;i++){
var param=methodCall.Method.GetParameters()[i];
var isReferenceType=!param.ParameterType.IsValueType;
var isNullable=isReferenceType||
(param.ParameterType.UnderlyingSystemType.IsGenericType&&
param.ParameterType.UnderlyingSystemType.GetGenericTypeDefinition()==typeof(Nullable<>));
var controllerParameterName=param.Name;
var routeDataContainsValueForParameterName=routeData.Values.ContainsKey(controllerParameterName);
var actual=routeData.Values.GetValue(controllerParameterName);
object expected=null;
var expressionToEvaluate=methodCall.Arguments[i];
// If the parameter is nullable and the expression is a Convert UnaryExpression,
// we actually want to test against the value of the expression's operand.
if(expressionToEvaluate.NodeType==ExpressionType.Convert
&&expressionToEvaluate is UnaryExpression)
expressionToEvaluate=((UnaryExpression)expressionToEvaluate).Operand;
switch(expressionToEvaluate.NodeType){
case ExpressionType.Constant:
expected=((ConstantExpression)expressionToEvaluate).Value;
break;
case ExpressionType.New:
case ExpressionType.MemberAccess:
expected=Expression.Lambda(expressionToEvaluate).Compile().DynamicInvoke();
break;
}
if(isNullable&&(string)actual==String.Empty&&expected==null)
// The parameter is nullable so an expected value of '' is equivalent to null;
continue;
// HACK: this is only sufficient while System.Web.Mvc.UrlParameter has only a single value.
if(actual==UrlParameter.Optional||
(actual!=null&&actual.ToString().Equals("System.Web.Mvc.UrlParameter")))
actual=null;
if (expected is DateTime)
actual = Convert.ToDateTime(actual);
else if (expected is User)
expected=((User)expected).Id.ToString();
else
expected = (expected == null ? expected : expected.ToString());
var errorMsgFmt="Value for parameter '{0}' did not match: expected '{1}' but was '{2}'";
if(routeDataContainsValueForParameterName)
errorMsgFmt+=".";
else
errorMsgFmt+=
"; no value found in the route context action parameter named '{0}' - does your matching route contain a token called '{0}'?";
//actualValue.ShouldEqual(expectedValue,
// string.Format(errorMsgFmt,controllerParameterName,expectedValue,actualValue));
//actualValue.Should().Be(expectedValue);
//if (expected!=null&&typeof(Entity).IsAssignableFm(expected.GetType()))
// expected=((Entity)expected).Id;
Assert.True(expected==null?actual==null:expected.Equals(actual),
string.Format(errorMsgFmt,controllerParameterName,expected,actual));
}
return routeData;
}
/// <summary>
/// Converts the URL to matching RouteData and verifies that it will match a route with the values specified by the expression.
/// </summary>
/// <typeparam name = "TController">The type of controller</typeparam>
/// <param name = "relativeUrl">The ~/ based url</param>
/// <param name = "action">The expression that defines what action gets called (and with which parameters)</param>
/// <returns></returns>
public static RouteData ShouldMapTo<TController>(this string relativeUrl,
Expression<Func<TController,ActionResult>> action)
where TController:Controller{
return relativeUrl.Route().ShouldMapTo(action);
}
/// <summary>
/// Verifies the <see cref = "RouteData">routeData</see> maps to the controller type specified.
/// </summary>
/// <typeparam name = "TController"></typeparam>
/// <param name = "routeData"></param>
/// <returns></returns>
public static RouteData ShouldMapTo<TController>(this RouteData routeData) where TController:Controller{
//strip out the word 'Controller' from the type
var expected=typeof(TController).Name.Replace("Controller","");
//get the key (case insensitive)
var actual=routeData.Values.GetValue("controller").ToString();
//actual.AssertSameStringAs(expected);
//actual.Should().Be(expected);
Assert.True(actual==expected,"{0}!={1}. Route maps to wrong controller. Action: {2}"
.With(actual,expected,routeData.Values["action"]));
return routeData;
}
/// <summary>
/// Verifies the <see cref = "RouteData">routeData</see> will instruct the routing engine to ignore the route.
/// </summary>
/// <param name = "relativeUrl"></param>
/// <returns></returns>
public static RouteData ShouldBeIgnored(this string relativeUrl){
var routeData=relativeUrl.Route();
//routeData.RouteHandler.ShouldBe<StopRoutingHandler>("Expected StopRoutingHandler, but wasn't");
routeData.RouteHandler.Should().Be.OfType<StopRoutingHandler>();
return routeData;
}
/// <summary>
/// Gets a value from the <see cref = "RouteValueDictionary" /> by key. Does a
/// case-insensitive search on the keys.
/// </summary>
/// <param name = "routeValues"></param>
/// <param name = "key"></param>
/// <returns></returns>
public static object GetValue(this RouteValueDictionary routeValues,string key){
foreach(var routeValueKey in routeValues.Keys)
if(string.Equals(routeValueKey,key,StringComparison.InvariantCultureIgnoreCase)){
if(routeValues[routeValueKey]==null)
return null;
return routeValues[routeValueKey].ToString();
}
return null;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment