BDD and ASP.NET MVC… a match made in heaven

1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 5 out of 5)
Loading ... Loading ...

Whilst preparing for my user group session on MVC I’ve upgraded my BDD base class (actually subclassed it) with specifics related to specifying the behavior of MVC routes and controllers.

I’ve made for starters 2 new classes:

public class MvcMockedHttpContext
{
    private readonly MockRepository _mocks;
    private IHttpContext _context;          

    public MvcMockedHttpContext(MockRepository mocks)
    {
        _mocks = mocks;
    }          

    private void _CreateMocked()
    {
        _context = _mocks.DynamicMock<IHttpContext>();          

        SetupResult.For(_context.Request)
		.Return(_mocks.DynamicMock<IHttpRequest>());
        SetupResult.For(_context.Response)
		.Return(_mocks.DynamicMock<IHttpResponse>());
        SetupResult.For(_context.Session)
		.Return(_mocks.DynamicMock<IHttpSessionState>());
        SetupResult.For(_context.Server)
		.Return(_mocks.DynamicMock<IHttpServerUtility>());          

        _mocks.Replay(_context);
    }          

    public IHttpContext ReturnMocked
    {
        get
        {
            if (_context == null) _CreateMocked();          

            return _context;
        }
    }
    public MvcMockedHttpContext FakeoutUrlRequest(string url)
    {
        if (_context == null) _CreateMocked();          

        SetupResult.For(_context.Request.AppRelativeCurrentExecutionFilePath)
                        .Return(url);
        SetupResult.For(_context.Request.PathInfo)
                        .Return(string.Empty);          

        return this;
    }
    public MvcMockedHttpContext FakeoutApplicationPath(string path)
    {
        if (_context == null) _CreateMocked();          

        SetupResult.For(_context.Request.ApplicationPath).Return(path);          

        return this;
    }          

}

Which I use to mock out the HttpRequest quickly and it serves me as a small mock out DSL for often used tasks with mocking the HttpRequest

Second part is the upgraded specification class:

public class MvcContextSpecification : ContextSpecification
{
    public MvcMockedHttpContext HttpContext
        {
            get
            {
                MvcMockedHttpContext context = new MvcMockedHttpContext(Mocks);
                return context;
            }
        }          

    public TController
                  CreateTestableControllerOf<TController>
                                                (string routeDataController,
                                                 string routeDataAction,
                                                 params object[]
                                                        paramsForControllerConstructor)
        where TController : Controller
    {
        TController controller =
                            CreateMockOf<TController>(paramsForControllerConstructor);          

        RouteData routeData = new RouteData();
        routeData.Values.Add(“Controller”, routeDataController);
        routeData.Values.Add(“Action”, routeDataAction);          

        IHttpContext context = HttpContext
                                    .FakeoutApplicationPath(“/”)
                                    .ReturnMocked;          

        controller.ControllerContext
                     = new ControllerContext(context, routeData, controller);          

        return controller;
    }          

    public bool HasActionActionAttribute<TController>
                                       (Expression<Action<TController>> action)
    {
        StringBuilder sb = new StringBuilder();
        MethodCallExpression call = action.Body as MethodCallExpression;
        if (call == null)
            throw new InvalidOperationException(“Expression must be a method call”);
        if (call.Object != action.Parameters[0])
            throw new InvalidOperationException(“Method call must target lambda argument”);          

        Attribute attribute
                       = Attribute.GetCustomAttribute(call.Method,
                                                      typeof(ControllerActionAttribute));          

        return (attribute != null);
    }
}

These 2 classes are my base for BDD with MVC (although admittedly this is for the combination of TDD and BDD, because although I am mostly testing the behavior at some parts I am testing the configuration as well)

Now comes the sweet part… how does this look implemented?

Let me show you an example of specifying the routes:

[Specification]
public void And_the_Selected_route_data_should_resolve_default
                                   _route_when_default_url_is_received()
{
    using (How_SUT_should_Behave)
    {
        _httpContext = HttpContext
                            .FakeoutUrlRequest(“~/default.aspx”)
                            .ReturnMocked;
    }          

    using (What_SUT_should_Result_to)
    {
        RouteData routeData = _routes.GetRouteData(_httpContext);          

        Assert.IsNotNull(routeData);
        Assert.AreEqual(“home”, routeData.Values["controller"]);
        Assert.AreEqual(“index”, routeData.Values["action"]);
    }
}

Sweet, isn’t it? Let me show the controllers:

[Context]
public class When_OrdersController_List_action_is_called
                                         : MvcContextSpecification
{
    [Specification]
    public void Then_List_view_should_be_displayed_and_OrdersService
                                                 _should_get_the_data_for_view()
    {
        IOrdersService mockedOrdersService = CreateMockOf<IOrdersService>();          

        OrdersController controller
                  = CreateTestableControllerOf<OrdersController>(“orders”,
                                                                 “list”,
                                                                 mockedOrdersService);          

        IList<Order> dummyOrders = _GetDummyOrders();          

        using (How_SUT_should_Behave)
        {
            Expect.Call(mockedOrdersService.ListAll()).Return(dummyOrders);          

            controller.RenderView(“List”, dummyOrders);
        }          

        using (What_SUT_should_Result_to)
        {
            controller.List();
        }
    }          

    [Specification]
    public void And_List_action_should_have_an_action_attribute()
    {
        Assert.IsTrue(HasActionActionAttribute<OrdersController>(x => x.List()));
    }          

    private List<Order> _GetDummyOrders()
        {
            return new List<Order>{
                            new Order(),
                            new Order(),
                            new Order()
                        };
        }
}

I’ve mentioned this to Joe Ocampo… I am struggling with myself to try to write the vanilla unit tests now for my session so that the attendees can more easily grasp the concepts in testing the MVC controllers and routes, and I must admit that… I DON’T WANT TOOO!!! I like my specifications, they feel so “right”.

What do you think? As with the previous BDD article… I would love your comments is this any good or bad?

Cheers!


Filed under: ASP.NET MVC, BDD, C#
Written on: 13 Jan 2008 ·

kick it on DotNetKicks.com

Leave a Reply