Faking the Environment
So the User object was now causing me grief. It was easy to understand why once I dug into it a little bit. ( I just now realized that this doens't need to be an injection, I should be able to just manipulate my fake HttpContext... but I digress... ) Well regardless of how the injection is handled, first we have to make a fake user with a fake userid, username, and roles. This stack overflow question got me what I needed. I wrapped it up in a static method that would let me pass in a userid and list of roles:
class FakeUser { public static IPrincipal GetFakeUser(int uid, string[] roles) { string username = "TestUser"; string userid = uid.ToString(); List<Claim> claims = new List<Claim>{ new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", username), new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", userid)}; var genericIdentity = new GenericIdentity(""); genericIdentity.AddClaims(claims); return new GenericPrincipal(genericIdentity, roles); } public static IPrincipal GetFakeUser(int uid) { return GetFakeUser(uid, new string[] { "TestRole" }); } }
This made it really easy for me to test behaviors based on ownership (using userid) and role membership. It wasn't enough. I hit another obstacle when I ran into this call:
var redirectUrl = new UrlHelper(Request.RequestContext).Action("UnitFocus", "UnitPersonnel", new { unitId = Unit_Id });
NullReferenceException... wtf? It's the Request.RequestContext that killed me. This is part of the HttpContext which doesn't exist when you new up a controller in a unit test. I was going to have to create a fake ControllerContext. Unfortunately, what little bit they did in OdeToFood was not nearly robust enough to help me, so again I had to go Googling until I found something. And find something I did. I found a Fake HttpRequest on GitHub, which was the major piece I was missing. I created a barebones fake HttpResponse before I realized that the same guy had already created a pretty good one of those too, (and a whole Fake HttpContext using them all...), but I got it working well enough anyway:
class FakeControllerContext : ControllerContext { HttpContextBase _context = new FakeHttpContext(); ControllerContext a = new ControllerContext(); public override System.Web.HttpContextBase HttpContext { get { return _context; } set { _context = value; } } } class FakeHttpContext : HttpContextBase { HttpRequestBase _request = new FakeHttpRequest("testrelativeurl", new System.Uri("http://testurl"), new System.Uri("http://testurlreferrer")); HttpResponseBase _response = new FakeHttpResponse(); public override HttpRequestBase Request { get { return _request; } } public override HttpResponseBase Response { get { return _response; } } public override object GetService(Type serviceType) { return null; } } public class FakeHttpRequest : HttpRequestBase { private readonly HttpCookieCollection _cookies; private readonly NameValueCollection _formParams; private readonly NameValueCollection _queryStringParams; private readonly NameValueCollection _serverVariables; private readonly string _relativeUrl; private readonly Uri _url; private readonly Uri _urlReferrer; private readonly string _httpMethod; private System.Web.Routing.RequestContext _requestContext; 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(); _requestContext = new System.Web.Routing.RequestContext(); } 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; } } public override System.Web.Routing.RequestContext RequestContext { get { _requestContext.HttpContext = new FakeHttpContext(); _requestContext.RouteData = new System.Web.Routing.RouteData(); _requestContext.RouteData.RouteHandler = new MvcRouteHandler(); var defaultDictionary = new System.Web.Routing.RouteValueDictionary(new { controller = "Home", action = "Index", id = new {} }); var route = new System.Web.Routing.Route("{controller}/{action}/{id}", defaultDictionary, new MvcRouteHandler()); _requestContext.RouteData.Values.Add("controller", "unitpersonnel"); _requestContext.RouteData.Values.Add("action", "addlistitem"); _requestContext.RouteData.Route = route; return _requestContext; } set { _requestContext = value; } } } public class FakeHttpResponse : HttpResponseBase { public override string ApplyAppPathModifier(string virtualPath) { return virtualPath; } }
Some of the routing stuff is probably unnecessary, it was part of the overall troubleshooting process. I eventually got it working when I added the routes within the test project. This code runs from the TestClass contructor:
try { RouteConfig.RegisterRoutes(RouteTable.Routes); } catch { //gulp }
I had to wrap it in a try-catch block because it was running it multiple times per run (probably because every test class contructor ran it...) and throwing a duplicate route exception. Again, probably a better way to handle it out there, but this is what I got working for me...
Next I'll go over the actual tests and the lessons learned.
No comments:
Post a Comment