Archive for December, 2007

Creating Patches for Subversion from Git

What a dreadful way to spend the last hour I wish it upon no one else. I recently started using Git and git-svn to track some of my projects and I almost completely abandoned it because creating an svn friendly patch that something like TortoiseMerge could apply is impossible. Then I found the git-svn-utils project. It doesn’t look like its active but it does have a script git-svn-diff which is exactly what I was looking for. Unfortunately the script was not handling new files the way subversion, or maybe TortoiseMerge wanted. So I made some pretty bad hacks and came up with this version. It will create a full patch file that can be applied with TSVN.

I am interested to know if I am the only one out there that had this problem or if there is a better solution. Please let me know as I am a total git newb.

Download git-svn-diff

[tag]git, svn[/tag]

MS MVC: Testing Routes

In Scott’s post about routing he showed how you can easily test routes. One cool thing about 3.5 asp.net extension framework is the introduction of new interfaces for IHttpContext, IHttpRequest, IHttpResponse, and some other. This makes testing with mocks so much easier. Here I will show you some tests I wrote to validate the routing rules we created for simply restful routing in the ms mvc framework.

What Are We Testing?

When testing we want to stick with one assertion per test. So for this example we are only testing that the rules we wrote set the proper action. You can view the rules we will be testing in my previous post.

Create The Test Fixture

using System.Collections.Specialized;
using System.Web;
using System.Web.Mvc;
using MVCContrib.SimplyRestful;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
using Rhino.Mocks;

namespace MVCContrib.Specs.SimplyRestfulSpecs
{
  [TestFixture]
  public class SimplyRestfulRouteMatchTests
  {
  }
}

Add The First Test

    [Test]
    public void GetRouteData_WithAControllerAndIdUsingAHttpGetRequest_SetsTheShowAction()
    {
    }

Our test name is using pattern Method_Context_Result. So the method under test is GetRouteData. The context of the test is With a controller and id using a an http get request sets the show action. Now lets fill in the test.

    [Test]
    public void GetRouteData_WithAControllerAndIdUsingAHttpGetRequest_SetsTheShowAction()
    {
      IHttpContext httpContext;
      RouteCollection routeCollection = new RouteCollection();
      SimplyRestfulRouteHandler.InitializeRoutes(routeCollection);
      RouteData routeData = routeCollection.GetRouteData(httpContext);
      Assert.That(routeData.Values["action"], Is.EqualTo("show").IgnoreCase);
    }

Above we wrote our assertion. We want to call the GetRouteData method on a routeCollection and we want to assert that the parameter named “action” is equal to show. At this point the test will fail with a nre on httpContext. httpContext needs to be an instance of IHttpContext, so how do we get an instance of an interface. Simple, we mock it. We will setup the mock context with just enough information for the RouteCollection to do its job. This could be considered a stub and not a mock since we are simply using Rhino.Mocks to setup results.

    [Test]
    public void GetRouteData_WithAControllerAndIdUsingAHttpGetRequest_SetsTheShowAction()
    {
      MockRepository mocks = new MockRepository();
      IHttpContext httpContext = mocks.DynamicMock<IHttpContext>();
      IHttpRequest httpRequest = mocks.DynamicMock<IHttpRequest>();

      RouteCollection routeCollection = new RouteCollection();
      SimplyRestfulRouteHandler.InitializeRoutes(routeCollection);

      using(mocks.Record())
      {
        SetupResult.For(httpContext.Request)
          .Return(httpRequest);
        SetupResult.For(httpRequest.AppRelativeCurrentExecutionFilePath)
          .Return("~/products/123");
        SetupResult.For(httpRequest.HttpMethod)
          .Return("GET");
      }
      using(mocks.Playback())
      {
        RouteData routeData = routeCollection.GetRouteData(httpContext);
        Assert.That(routeData.Values["action"], Is.EqualTo("show").IgnoreCase);
      }
    }

So what just happened? First we created a MockRepository which is the core engine of Rhino.Mocks. This could probably get moved to a [SetUp] method. Next we create two Dynamic Mocks. One for the context and one for the request. We make them dynamic mocks because they are not under test. We do not care what gets called on these interfaces or how it gets called. We just care that if certain properties are called they return a specific values emulating what a real http request would look like. Next we have the mocks.Record phase. This is used to demarcate expectations versus assertions.

What to note is that the RouteCollection uses the AppRelativeCurrentExecutionFilePath property which expects a return string like “~/something/something/something.aspx” we are telling it to return “~/products/123″ products is our controller and 123 should be our id of the route. Next we setup a result for the Httpmethod property, remember our Route had validation on the special name Method which maps to a call on HttpMethod.

Finally we run our test and make the assertions. We enter playback mode, execute the GetRouteData method, which is our method under test, pass in the mocked context and assert that the returned routedata has a parameter named “action” = “show”

So once you add a couple more tests for different routes you will see a way to factor some common code out. I created a helper method to setup my context.

    private void SetupContext(string url, string httpMethod, string formMethod)
    {
      SetupResult.For(httpContext.Request).Return(httpRequest);
      SetupResult.For(httpRequest.AppRelativeCurrentExecutionFilePath).Return(url);
      SetupResult.For(httpRequest.HttpMethod).Return(httpMethod);
      if(!string.IsNullOrEmpty(formMethod))
      {
        NameValueCollection form = new NameValueCollection();
        form.Add("_method", formMethod);
        SetupResult.For(httpRequest.Form).Return(form);
      }
    }

Then my tests simply look like this.

    [Test]
    public void GetRouteData_WithAControllerAndIdUsingFormMethodPut_UsesSimplyRestfulRouteHandler()
    {
      using (mocks.Record())
      {
        SetupContext("~/controller/123", "POST", "PUT");
      }
      using (mocks.Playback())
      {
        RouteData routeData = routeCollection.GetRouteData(httpContext);
        Assert.That(routeData.Route.RouteHandler, Is.EqualTo(typeof(SimplyRestfulRouteHandler)));
      }
    }

The best part about writing tests for this stuff is you no longer have to fire up a browser and test by hand. You get instant verification that runs quick.

If you are wondering how I figured out what to mock and setup results for it was a quick perusal of System.Web.Extensions in reflector. Having to open reflector to figure out what to mock is a code smell for me. You really shouldn’t be mocking things you don’t own or control but this is a nice quick integration test that can save you tons of time down the road. Just don’t be surprised if after an update you get some errors because MS changed the implementation of RouteCollection.GetRouteData, another great reason for using a ContextSetup method.

MS MVC: Simply Restful Routing

MS MVC first public code drop is available. I hope you have read through all of ScottGu’s posts to help prep you for the drop. If you haven’t I highly suggest reading about Routing first before you dive into this.

What we are going to do is build a Simply Restful Route Handler similar to what is available in rails. MS MVC supports restful routing pretty good out of the box. The only case it doesn’t pick up is when browsers cannot send real HTTP PUT and DELETE requests. To get around this we are going to build a very simple route handler to inspect a hidden field on the submitted form to set the intended action of the request.

We will be creating 8 restful routes, contrary to the 7 routes in simply restful.

The Eight Actions

  • Show : handles a GET request for a displaying a single resource that the controller is representing.
  • Create : handles a POST request for a creating a new resource.
  • Update : handles a PUT request for updating an existing resource.
  • Destroy : handles a DELETE request on a resource.
  • Index : handles a GET request for a collection of resources.
  • New : handles a GET request for a blank form for creating a new resource
  • Edit : handles a GET request for a form with values filled in from the resource for updating.
  • Delete : handles a GET request a confirmation / form with options for deleting a resource. * This is the extra action.

The Routes

Action Url Http Method Form Method
Show [controller]/[id] GET
Create [controller] POST
Update [controller]/[id] PUT
Update [controller]/[id] POST PUT
Destroy [controller]/[id] DELETE
Destroy [controller]/[id] POST DELETE
Index [controller] GET
New [controller]/new GET
Edit [controller]/[id]/edit GET
Delete [controller]/[id]/delete GET

The Simply Restful Routes In MS MVC

routeCollection.Add(new Route
{
  Url = "[controller]/new",
  Defaults = new { Action = "new" },
  RouteHandler = typeof(MvcRouteHandler)
});
routeCollection.Add(new Route
{
  Url = "[controller]/[id]/[action]",
  Validation = new
  {
    Method = "GET",
    Id = idValidationRegex ?? MatchAny,
    Action = "[eE][dD][iI][tT]|[dD][eE][lL][eE][tT][eE]"
  },
  RouteHandler = typeof(MvcRouteHandler)
});

routeCollection.Add(new Route
{
  Url = "[controller]/[id]",
  Validation = new
  {
    Method = "POST",
    Id = idValidationRegex ?? MatchAny,
  },
  RouteHandler = typeof(SimplyRestfulRouteHandler)
});

routeCollection.Add(new Route
{
  Url = "[controller]/[id]",
  Defaults = new { Action = "show" },
  Validation = new
  {
    Method = "GET",
    Id = idValidationRegex ?? MatchAny,
  },
  RouteHandler = typeof(MvcRouteHandler)
});

routeCollection.Add(new Route
{
  Url = "[controller]/[id]",
  Defaults = new { Action = "update" },
  Validation = new
  {
    Method = "PUT",
    Id = idValidationRegex ?? MatchAny
  },
  RouteHandler = typeof(MvcRouteHandler)
});

routeCollection.Add(new Route
{
  Url = "[controller]/[id]",
  Defaults = new { Action = "destroy" },
  Validation = new
  {
    Method = "DELETE",
    Id = idValidationRegex ?? MatchAny
  },
  RouteHandler = typeof(MvcRouteHandler)
});

routeCollection.Add(new Route
{
  Url = "[controller]",
  Defaults = new { Action = "index" },
  Validation = new { Method = "GET" },
  RouteHandler = typeof(MvcRouteHandler)
});

routeCollection.Add(new Route
{
  Url = "[controller]",
  Defaults = new { Action = "create" },
  Validation = new { Method = "POST" },
  RouteHandler = typeof(MvcRouteHandler)
});

I am not going to explain much of what is going on above, have a look at ScottGu’s post on routing for that. What I will point out are a couple things. First we use use some hard matches like [controller]/new and map that with a Default action. Very straightforward but just a good example of how Routes and Defaults work. Second we add some validation. Scott covered the standard validation that accepts a regex. You will notice that the regex is case sensitive so we end up with a pretty wacky looking string for “Edit|Delete”. Third, we are using a special validation property named Method which will match on the HttpMethod of the Request. And finally notice we only have a single rule that will require a custom IRouteHandler.

So lets start with a story for our new custom route handler:

Story: RouteHandler Assigns Restful Actions

As a Route Handler
I want standard post requests to route to restful actions.
So that people without javascript enabled or incompatible browsers can still have a restful experience and my web server needs to issue less browser redirects.

Acceptance Criteria:

Scenario 1: Form _method PUT Triggers Update Action
GIVEN the form has a field named _method with a value of PUT
AND the url matches [controller]/[id]
AND the HTTP method is POST
WHEN the form is posted
THEN the route action should be Update

Scenario 2: DELETE Triggers Destroy Action
GIVEN the form has a field named _method with a value of DELETE
AND the url matches [controller]/[id]
AND the HTTP method is POST
WHEN the form is posted
THEN the route action should be Delete

TDD First

To make testing easier I will be using Rhino.Mocks to create dynamic mocks of the IHttpContext and IHttpRequest. Thanks Microsoft for finally giving us interfaces to make our testing easier.

[TestFixture]
[Category("SimplyRestfulSpecs")]
public class When_The_Form_Is_Posted_With_A_Form_Field_Named_Method_And_A_Value_Of_PUT
: BaseRouteHandlerTestFixture
{
[SetUp]
protected override void GivenSetupContext()
{
  base.GivenSetupContext();
  form.Add("_method", "PUT");
}

[Test]
public void Then_The_Route_Action_Should_Be_Set_To_Update()
{
  RestfulAction action = RestfulAction.None;
  IRestfulActionResolver resolver = new RestfulActionResolver();

  using(mocks.Record())
  {
    SetupResult.For(httpContext.Request).Return(httpRequest);
    SetupResult.For(httpRequest.RequestType).Return("POST");
    SetupResult.For(httpRequest.Form).Return(form);
    requestContext = new RequestContext(httpContext, routeData);
  }

  using(mocks.Playback())
  {
    action = resolver.ResolveAction(requestContext);
    Assert.That(action, Is.EqualTo(RestfulAction.Update));
  }
}
}

[TestFixture]
[Category("SimplyRestfulSpecs")]
public class When_The_Form_Is_Posted_With_A_Form_Field_Named_Method_And_A_Value_Of_DELETE
: BaseRouteHandlerTestFixture
{
[SetUp]
protected override void GivenSetupContext()
{
  base.GivenSetupContext();
  form.Add("_method", "DELETE");
}

[Test]
public void Then_The_Route_Action_Should_Be_Set_To_Destroy()
{
  RestfulAction action = RestfulAction.None;
  IRestfulActionResolver resolver = new RestfulActionResolver();

  using (mocks.Record())
  {
    SetupResult.For(httpContext.Request).Return(httpRequest);
    SetupResult.For(httpRequest.RequestType).Return("POST");
    SetupResult.For(httpRequest.Form).Return(form);
    requestContext = new RequestContext(httpContext, routeData);
  }

  using (mocks.Playback())
  {
    action = resolver.ResolveAction(requestContext);
    Assert.That(action, Is.EqualTo(RestfulAction.Destroy));
  }
}
}
  #region BaseRouteHandlerTestFixture
  public abstract class BaseRouteHandlerTestFixture
  {
    protected MockRepository mocks;
    protected IHttpContext httpContext;
    protected IHttpRequest httpRequest;
    protected RouteData routeData;
    protected RequestContext requestContext;
    protected NameValueCollection form;

    protected virtual void GivenSetupContext()
    {
      mocks = new MockRepository();
      httpContext = mocks.DynamicMock<IHttpContext>();
      httpRequest = mocks.DynamicMock<IHttpRequest>();

      routeData = new RouteData();
      routeData.Values.Add("controller", "testcontroller");
      routeData.Values.Add("action", "update");

      form = new NameValueCollection();
    }
  }
  #endregion

The Code

public enum RestfulAction
{
None = 16384,
Show = 1,
Create = 2,
Update = 4,
Destroy = 8,
Index = 16,
New = 32,
Edit = 64,
Delete = 128
}

public class SimplyRestfulRouteHandler : MvcRouteHandler
{
private const string MatchAny = “*“;

private IRestfulActionResolver actionResolver;

public SimplyRestfulRouteHandler()
{
actionResolver = new RestfulActionResolver();
}

public SimplyRestfulRouteHandler(IRestfulActionResolver actionResolver)
{
this.actionResolver = actionResolver;
}

protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
EnsureActionResolver(requestContext.HttpContext);

RestfulAction action = actionResolver.ResolveAction(requestContext);
if (action != RestfulAction.None)
{
requestContext.RouteData.Values["action"] = action.ToString();
}
return base.GetHttpHandler(requestContext);
}

private void EnsureActionResolver(IServiceProvider serviceProvider)
{
if (actionResolver == null)
{
actionResolver = (IRestfulActionResolver)
serviceProvider.GetService(typeof(IRestfulActionResolver));
if (actionResolver == null)
actionResolver = new RestfulActionResolver();
}
}
}

public interface IRestfulActionResolver
{
RestfulAction ResolveAction(RequestContext context);
}

public class RestfulActionResolver : IRestfulActionResolver
{
public RestfulAction ResolveAction(RequestContext context)
{
if (context == null ||
context.HttpContext == null ||
context.HttpContext.Request == null)
{
throw new NullReferenceException(“Request in current HttpContext cannot be null.“);
}

if (string.IsNullOrEmpty(context.HttpContext.Request.RequestType))
{
return RestfulAction.None;
}

string requestType = context.HttpContext.Request.RequestType.ToLowerInvariant();
if (string.Equals(requestType, “post“, StringComparison.Ordinal))
{
return ResolvePostAction(context);
}

return RestfulAction.None;
}

private static RestfulAction ResolvePostAction(RequestContext context)
{
if (context.HttpContext.Request.Form == null)
{
return RestfulAction.None;
}

string formMethod = context.HttpContext.Request.Form["_method"];
if (string.IsNullOrEmpty(formMethod))
{
return RestfulAction.None;
}

formMethod = formMethod.Trim();
if (string.Equals(“put“, formMethod, StringComparison.OrdinalIgnoreCase))
{
return RestfulAction.Update;
}
else if(string.Equals(“delete“, formMethod, StringComparison.OrdinalIgnoreCase))
{
return RestfulAction.Destroy;
}

return RestfulAction.None;
}
}

Conclusion

The only opinion I have so far on MS-MVC is it beats the heck out of WebForms and I am glad to see MS is committed to supporting a product that fits this space. In the coming weeks I should have much more about the framework. I just wanted to get something usable out to the community first.

I hope to get this stuff into the new MVC Contrib project as well.

Whats next, maybe some form helpers, templates, and a base controller to make working with the views and actions a little easier.