Skip to content

Commit

Permalink
initial commit, as downloaded with no changes
Browse files Browse the repository at this point in the history
  • Loading branch information
gregoryjscott committed Oct 13, 2010
0 parents commit 2d9ed6a
Show file tree
Hide file tree
Showing 34 changed files with 10,142 additions and 0 deletions.
32 changes: 32 additions & 0 deletions IntegrationTestingExample.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

Microsoft Visual Studio Solution File, Format Version 10.00
# Visual Studio 2008
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyMvcApplication", "MyMvcApplication\MyMvcApplication.csproj", "{DA471BE6-C786-4D16-A390-3111F9E86B66}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyMvcApplication.Tests", "MyMvcApplication.Tests\MyMvcApplication.Tests.csproj", "{337FEBC1-F001-444B-BF72-A4380F5EA658}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvcIntegrationTestFramework", "MvcIntegrationTestFramework\MvcIntegrationTestFramework.csproj", "{4036AFF9-698B-463D-BE26-05D2CE3E2ABD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DA471BE6-C786-4D16-A390-3111F9E86B66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DA471BE6-C786-4D16-A390-3111F9E86B66}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DA471BE6-C786-4D16-A390-3111F9E86B66}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DA471BE6-C786-4D16-A390-3111F9E86B66}.Release|Any CPU.Build.0 = Release|Any CPU
{337FEBC1-F001-444B-BF72-A4380F5EA658}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{337FEBC1-F001-444B-BF72-A4380F5EA658}.Debug|Any CPU.Build.0 = Debug|Any CPU
{337FEBC1-F001-444B-BF72-A4380F5EA658}.Release|Any CPU.ActiveCfg = Release|Any CPU
{337FEBC1-F001-444B-BF72-A4380F5EA658}.Release|Any CPU.Build.0 = Release|Any CPU
{4036AFF9-698B-463D-BE26-05D2CE3E2ABD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4036AFF9-698B-463D-BE26-05D2CE3E2ABD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4036AFF9-698B-463D-BE26-05D2CE3E2ABD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4036AFF9-698B-463D-BE26-05D2CE3E2ABD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
86 changes: 86 additions & 0 deletions MvcIntegrationTestFramework/Browsing/BrowsingSession.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System;
using System.Collections.Specialized;
using System.IO;
using System.Web;
using System.Web.Mvc;
using System.Web.SessionState;
using MvcIntegrationTestFramework.Interception;

namespace MvcIntegrationTestFramework.Browsing
{
public class BrowsingSession
{
public HttpSessionState Session { get; private set; }
public HttpCookieCollection Cookies { get; private set; }

public BrowsingSession()
{
Cookies = new HttpCookieCollection();
}

public RequestResult ProcessRequest(string url)
{
return ProcessRequest(url, HttpVerbs.Get, null);
}

public RequestResult ProcessRequest(string url, HttpVerbs httpVerb, NameValueCollection formValues)
{
return ProcessRequest(url, httpVerb, formValues, null);
}

public RequestResult ProcessRequest(string url, HttpVerbs httpVerb, NameValueCollection formValues, NameValueCollection headers)
{
if (url == null) throw new ArgumentNullException("url");

// Fix up URLs that incorrectly start with / or ~/
if (url.StartsWith("~/"))
url = url.Substring(2);
else if(url.StartsWith("/"))
url = url.Substring(1);

// Parse out the querystring if provided
string query = "";
int querySeparatorIndex = url.IndexOf("?");
if (querySeparatorIndex >= 0) {
query = url.Substring(querySeparatorIndex + 1);
url = url.Substring(0, querySeparatorIndex);
}

// Perform the request
LastRequestData.Reset();
var output = new StringWriter();
string httpVerbName = httpVerb.ToString().ToLower();
var workerRequest = new SimulatedWorkerRequest(url, query, output, Cookies, httpVerbName, formValues, headers);
HttpRuntime.ProcessRequest(workerRequest);

// Capture the output
AddAnyNewCookiesToCookieCollection();
Session = LastRequestData.HttpSessionState;
return new RequestResult
{
ResponseText = output.ToString(),
ActionExecutedContext = LastRequestData.ActionExecutedContext,
ResultExecutedContext = LastRequestData.ResultExecutedContext,
Response = LastRequestData.Response,
};
}

private void AddAnyNewCookiesToCookieCollection()
{
if(LastRequestData.Response == null)
return;

HttpCookieCollection lastResponseCookies = LastRequestData.Response.Cookies;
if(lastResponseCookies == null)
return;

foreach (string cookieName in lastResponseCookies) {
HttpCookie cookie = lastResponseCookies[cookieName];
if (Cookies[cookieName] != null)
Cookies.Remove(cookieName);
if((cookie.Expires == default(DateTime)) || (cookie.Expires > DateTime.Now))
Cookies.Add(cookie);
}
}
}
}
16 changes: 16 additions & 0 deletions MvcIntegrationTestFramework/Browsing/MvcUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Text.RegularExpressions;

namespace MvcIntegrationTestFramework.Browsing
{
public static class MvcUtils
{
public static string ExtractAntiForgeryToken(string htmlResponseText)
{
if (htmlResponseText == null) throw new ArgumentNullException("htmlResponseText");

Match match = Regex.Match(htmlResponseText, @"\<input name=""__RequestVerificationToken"" type=""hidden"" value=""([^""]+)"" \/\>");
return match.Success ? match.Groups[1].Captures[0].Value : null;
}
}
}
16 changes: 16 additions & 0 deletions MvcIntegrationTestFramework/Browsing/RequestResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Web;
using System.Web.Mvc;

namespace MvcIntegrationTestFramework.Browsing
{
/// <summary>
/// Represents the result of a simulated request
/// </summary>
public class RequestResult
{
public HttpResponse Response { get; set; }
public string ResponseText { get; set; }
public ActionExecutedContext ActionExecutedContext { get; set; }
public ResultExecutedContext ResultExecutedContext { get; set; }
}
}
88 changes: 88 additions & 0 deletions MvcIntegrationTestFramework/Browsing/SimulatedWorkerRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using System.Web;
using System.Web.Hosting;
using System.Linq;

namespace MvcIntegrationTestFramework.Browsing
{
internal class SimulatedWorkerRequest : SimpleWorkerRequest
{
private HttpCookieCollection cookies;
private readonly string httpVerbName;
private readonly NameValueCollection formValues;
private readonly NameValueCollection headers;

public SimulatedWorkerRequest(string page, string query, TextWriter output, HttpCookieCollection cookies, string httpVerbName, NameValueCollection formValues, NameValueCollection headers)
: base(page, query, output)
{
this.cookies = cookies;
this.httpVerbName = httpVerbName;
this.formValues = formValues;
this.headers = headers;
}

public override string GetHttpVerbName()
{
return httpVerbName;
}

public override string GetKnownRequestHeader(int index)
{
// Override "Content-Type" header for POST requests, otherwise ASP.NET won't read the Form collection
if (index == 12)
if (string.Equals(httpVerbName, "post", StringComparison.OrdinalIgnoreCase))
return "application/x-www-form-urlencoded";

switch (index) {
case 0x19:
return MakeCookieHeader();
default:
if (headers == null)
return null;
return headers[GetKnownRequestHeaderName(index)];
}
}

public override string GetUnknownRequestHeader(string name)
{
if(headers == null)
return null;
return headers[name];
}

public override string[][] GetUnknownRequestHeaders()
{
if (headers == null)
return null;
var unknownHeaders = from key in headers.Keys.Cast<string>()
let knownRequestHeaderIndex = GetKnownRequestHeaderIndex(key)
where knownRequestHeaderIndex < 0
select new[] { key, headers[key] };
return unknownHeaders.ToArray();
}

public override byte[] GetPreloadedEntityBody()
{
if(formValues == null)
return base.GetPreloadedEntityBody();

var sb = new StringBuilder();
foreach (string key in formValues)
sb.AppendFormat("{0}={1}&", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(formValues[key]));
return Encoding.UTF8.GetBytes(sb.ToString());
}

private string MakeCookieHeader()
{
if((cookies == null) || (cookies.Count == 0))
return null;
var sb = new StringBuilder();
foreach (string cookieName in cookies)
sb.AppendFormat("{0}={1};", cookieName, cookies[cookieName].Value);
return sb.ToString();
}
}
}
27 changes: 27 additions & 0 deletions MvcIntegrationTestFramework/Hosting/AppDomainProxy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using MvcIntegrationTestFramework.Browsing;

namespace MvcIntegrationTestFramework.Hosting
{
/// <summary>
/// Simply provides a remoting gateway to execute code within the ASP.NET-hosting appdomain
/// </summary>
internal class AppDomainProxy : MarshalByRefObject
{
public void RunCodeInAppDomain(Action codeToRun)
{
codeToRun();
}

public void RunBrowsingSessionInAppDomain(SerializableDelegate<Action<BrowsingSession>> script)
{
var browsingSession = new BrowsingSession();
script.Delegate(browsingSession);
}

public override object InitializeLifetimeService()
{
return null; // Tells .NET not to expire this remoting object
}
}
}
111 changes: 111 additions & 0 deletions MvcIntegrationTestFramework/Hosting/AppHost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Hosting;
using System.Web.Mvc;
using MvcIntegrationTestFramework.Browsing;
using MvcIntegrationTestFramework.Interception;

namespace MvcIntegrationTestFramework.Hosting
{
/// <summary>
/// Hosts an ASP.NET application within an ASP.NET-enabled .NET appdomain
/// and provides methods for executing test code within that appdomain
/// </summary>
public class AppHost
{
private readonly AppDomainProxy appDomainProxy; // The gateway to the ASP.NET-enabled .NET appdomain

public AppHost(string appPhysicalDirectory) : this(appPhysicalDirectory, "/")
{
}

public AppHost(string appPhysicalDirectory, string virtualDirectory)
{
try {
appDomainProxy = (AppDomainProxy) ApplicationHost.CreateApplicationHost(typeof (AppDomainProxy), virtualDirectory, appPhysicalDirectory);
} catch(FileNotFoundException ex) {
if((ex.Message != null) && ex.Message.Contains("MvcIntegrationTestFramework"))
throw new InvalidOperationException("Could not load MvcIntegrationTestFramework.dll within a bin directory under " + appPhysicalDirectory + ". Is this the path to your ASP.NET MVC application, and have you set up a post-build event to copy your test assemblies and their dependencies to this folder? See the demo project for an example.");
throw;
}

appDomainProxy.RunCodeInAppDomain(() => {
InitializeApplication();
AttachTestControllerDescriptorsForAllControllers();
LastRequestData.Reset();
});
}

public void SimulateBrowsingSession(Action<BrowsingSession> testScript)
{
var serializableDelegate = new SerializableDelegate<Action<BrowsingSession>>(testScript);
appDomainProxy.RunBrowsingSessionInAppDomain(serializableDelegate);
}

#region Initializing app & interceptors
private static void InitializeApplication()
{
var appInstance = GetApplicationInstance();
appInstance.PostRequestHandlerExecute += delegate {
// Collect references to context objects that would otherwise be lost
// when the request is completed
if(LastRequestData.HttpSessionState == null)
LastRequestData.HttpSessionState = HttpContext.Current.Session;
if (LastRequestData.Response == null)
LastRequestData.Response = HttpContext.Current.Response;
};
RefreshEventsList(appInstance);

RecycleApplicationInstance(appInstance);
}

private static void AttachTestControllerDescriptorsForAllControllers()
{
var allControllerTypes = from assembly in AppDomain.CurrentDomain.GetAssemblies()
from type in assembly.GetTypes()
where typeof (IController).IsAssignableFrom(type)
select type;
foreach (var controllerType in allControllerTypes)
InterceptionFilter.AssociateWithControllerType(controllerType);
}
#endregion

#region Reflection hacks
private static readonly MethodInfo getApplicationInstanceMethod;
private static readonly MethodInfo recycleApplicationInstanceMethod;

static AppHost()
{
// Get references to some MethodInfos we'll need to use later to bypass nonpublic access restrictions
var httpApplicationFactory = typeof(HttpContext).Assembly.GetType("System.Web.HttpApplicationFactory", true);
getApplicationInstanceMethod = httpApplicationFactory.GetMethod("GetApplicationInstance", BindingFlags.Static | BindingFlags.NonPublic);
recycleApplicationInstanceMethod = httpApplicationFactory.GetMethod("RecycleApplicationInstance", BindingFlags.Static | BindingFlags.NonPublic);
}

private static HttpApplication GetApplicationInstance()
{
var writer = new StringWriter();
var workerRequest = new SimpleWorkerRequest("", "", writer);
var httpContext = new HttpContext(workerRequest);
return (HttpApplication)getApplicationInstanceMethod.Invoke(null, new object[] { httpContext });
}

private static void RecycleApplicationInstance(HttpApplication appInstance)
{
recycleApplicationInstanceMethod.Invoke(null, new object[] { appInstance });
}

private static void RefreshEventsList(HttpApplication appInstance)
{
object stepManager = typeof (HttpApplication).GetField("_stepManager", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(appInstance);
object resumeStepsWaitCallback = typeof(HttpApplication).GetField("_resumeStepsWaitCallback", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(appInstance);
var buildStepsMethod = stepManager.GetType().GetMethod("BuildSteps", BindingFlags.NonPublic | BindingFlags.Instance);
buildStepsMethod.Invoke(stepManager, new[] { resumeStepsWaitCallback });
}

#endregion
}
}
Loading

0 comments on commit 2d9ed6a

Please sign in to comment.