Home > .net 3.5, c#, Microsoft, MVC, Visual Studio 2008 > The .NET MVC Framework – Part 2.1 – Mocking Query Strings in Routes

The .NET MVC Framework – Part 2.1 – Mocking Query Strings in Routes

December 18, 2007

This post is more of an explaination from Part 2 of what I’m trying to do, why, and an email post to Phil Haack.  I’m sure the novel email I sent made him cringe. πŸ™‚

[Update 19 Dec 07: 

Phil Haack responded with some very useful information.  Heh, quite honeslty, I misunderstood exactly what I was doing (not the first time)—and was taking mocking a bit too far.  Since we’re mocking up and testing routes, that’s exactly what works—testing the route of a URL to a specified Controller and Action.  Beyond that, which would be passing parameters (aka Data) to the methods is beyond the scope of those tests and would be better handled elsewhere.  Thanks for clearing that up Phil.  Now, to find a good place to actually test full Urls to make sure that the correct parameters are being set.

So, for now, the code below is correct and worthwhile if you wanted to test a Url like “http://host/galleries?action=catchhandler” or “http://host/default.aspx?id=1”; but they must fit into the route itself—which is exactly what we’re testing.

*lightbulb* :)]

My pain revolves around the query string and how to fetch those parameters just as I would path-based parameters since, when hitting the ControllerAction, they are treated the same.

So, I have a route:

routes.Add(new Route

{

Url = “WebStorageHandler.ashx”,

       Defaults = new

       {

             controller = “Galleries”,

             action = “CatchHandler”

},

       RouteHandler = defaultRouteHandler

});

I want to be able to mock up something similar to what was in the Part 2 post; however, I want to pass along an Id.  With this, my expectations are that the “Galleries” controller would be accessed, the “CatchHandler” action would be called, and the Id of “166” would be passed—generating /Galleries/CatchHandler/166.

[TestMethod]

public void CanMap_OldWebStorageHandler()

{

TestHelper.AssertRoute(BuildDefaultRoutes(),

       “~/WebStorageHandler.ashx?id=166&tb=false&type=Photo”,

       new { controller = “galleries”,

action = “catchhandler”,

id = “166” });

}.

 

Taking the Id out of the query string and returning to path-based navigation works just fine:

 

[TestMethod]

public void CanMap_CatchHandlerWithId()

{

TestHelper.AssertRoute(BuildDefaultRoutes(),

       “~/galleries/catchhandler/166”,

       new { controller = “galleries”, action = “catchhandler”, id = “166” });

}

QueryString fails, but pathing doesn't.

 

Even though the Id isn’t part of the default path, it is part of the CatchHandler action—and is a valid route (as noted in the Part #2 post).

Unfortunately, the DynamicIHttpContext method in Phil’s mockup didn’t take the QueryString into consideration; so here’s what I changed.  Note, this is just prototyping and trying to get it working—there’s a bit here that needs some tidying up.

public static IHttpContext DynamicIHttpContext(this MockRepository mocks,

NameValueCollection queryString, string url)

{

IHttpContext context = mocks.DynamicMock<IHttpContext>();

       IHttpRequest request = mocks.DynamicMock<IHttpRequest>();

       IHttpResponse response = mocks.DynamicMock<IHttpResponse>();

       IHttpSessionState session = mocks.DynamicMock<IHttpSessionState>();

       IHttpServerUtility server = mocks.DynamicMock<IHttpServerUtility>();

 

       SetupResult.For(request.QueryString).Return(queryString);

       SetupResult.For(request.AppRelativeCurrentExecutionFilePath).Return(url);

       SetupResult.For(request.PathInfo).Return(string.Empty);

       mocks.Replay(request);

 

       SetupResult.For(context.Request).Return(request);

       SetupResult.For(context.Response).Return(response);

       SetupResult.For(context.Session).Return(session);

       SetupResult.For(context.Server).Return(server);

 

       mocks.Replay(context);

       return context;

}

I pulled the logic for IHttpRequest into the method so I could be sure to set them and THEN pass it to the context object.  I’m not sure if that’s necessary (my RhinoMocks experience is average at best :(), but it worked. πŸ™‚

That leaves the calling method to look a bit different:

public static IHttpContext DynamicIHttpContext(

this MockRepository mocks, string url)

{

if (url == null)

              throw new ArgumentNullException(“url”);

 

if (!url.StartsWith(“~/”))

              throw new ArgumentException(

“Sorry, we expect a virtual url starting with \”~/\”.”);

 

IHttpContext context =

DynamicIHttpContext(mocks,

GetQueryStringParameters(url), GetUrlFileName(url));

 

return context;

}

The GetQueryStringParameters and GetUrlFileName are quick helper methods I wrote up because, quite frankly, it was far too early in the morning and I needed a bit of parsing.  Here they are for reference:

public static string GetUrlFileName(string url)

{

if (url.Contains(“?”))

              return url.Substring(0, url.IndexOf(“?”));

else

              return url;

}

 

public static NameValueCollection GetQueryStringParameters(string url)

{

if (url.Contains(“?”))

       {

              NameValueCollection parameters = new NameValueCollection();

 

             string[] parts = url.Split(“?”.ToCharArray());

             string[] keys = parts[1].Split(“&”.ToCharArray());

 

             foreach (string key in keys)

             {

                     string[] part = key.Split(“=”.ToCharArray());

                    parameters.Add(part[0], part[1]);

}

 

             return parameters;

}

else

       {

              return null;

}

}

GetUrlFileName separates out the query string from the Url file name, leaving “~/FileName.aspx”. 

GetQueryStringParameters generates a NameValueCollection, which is what IHttpRequest.QueryString is, and slugs it with the keys/values of the Url.

That all works great.  It mocks up the IHttpContext correctly, passes along the query string, and gets as far as where it’s matching up the RouteData’s property bag to the expectations you specified in your assertion.

foreach (PropertyValue property in GetProperties(expectations))

{

Assert.IsTrue(string.Equals(

(string)property.Value,

(string)routeData.Values[property.Name],

StringComparison.InvariantCultureIgnoreCase),

string.Format(

“Did not expect ‘{0}’ for ‘{1}’.”,

property.Value, property.Name));

}

Since the parameters for the ControllerAction do not exist in the Values bag of RouteData, it throws an exception:

System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.. 

So, the question I present—is there a better way? 

I’d RATHER mock up my routes against the true ControllerAction and see what Urls are being generated rather than simply looking at the default route—especially since it works outside the mock up. πŸ™‚