Tuesday, July 19, 2011

How to mock UrlHelper?

Sometime, you need to write unit test for an action method that needs a UrlHelper. But I'm sure you will never can mock that stuff since it's a concrete class and it's methods are not virtual. Let's say we have following action method and want to test it:

public ActionResult LogOnAs(Guid userId, string returnUrl)
    // Logic to login user by Id ...
    if (Url.IsLocalUrl(returnUrl))
        return Redirect(returnUrl);

    return RedirectToAction("Index", "User");
I did some research, experimented with some results and find that extracting the UrlHelper methods to an interface then make a wrapper class to implement that interface is the best way. We need to define a new property to the BaseController like below.
public new IUrlHelper Url {get; set;}
Then in the unit test project, after initializing the controller, you can mock the interface easily. You probably need only some methods from the UrlHelper so I would recommend you extract only those required methods to IUrlHelper. For example:
public interface IUrlHelper
    string Action(string actionName, string controllerName);

    string Action(string actionName, string controllerName, object routeValues);

    string Action(string actionName, string controllerName, RouteValueDictionary routeValues);
    bool IsLocalUrl(string url);
Finally, we can create an adaptor class like below to delegate all method calls to the real UrlHelper object:
public class UrlHelperAdaptor : UrlHelper, IUrlHelper
    internal UrlHelperAdaptor(RequestContext requestContext)
        : base(requestContext)

    internal UrlHelperAdaptor(RequestContext requestContext, RouteCollection routeCollection)
        : base(requestContext, routeCollection)

    public UrlHelperAdaptor(UrlHelper helper) 
        : base(helper.RequestContext, helper.RouteCollection)
Apparently, we need to initialize the new Url property in the BaseController to make the real code work normally:
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
    Url = new UrlHelperAdaptor(base.Url);
Now, the controller method is fully testable.
public void LogonAs_should_return_RedirectToRouteResult()
    // Arrange
    var controller = new AccountController();
    /* Create a mock of IUrlHelper */
    controller.Url = Moq.Mock.Of<IUrlHelper>(x => x.IsLocalUrl(It.IsAny<string>()) == false);     

    // Action
    var result = controller.LogOnAs(Guid.NewGuid(), "any-return-url") as RedirectToRouteResult;

    // Assert
Using this approach can help you test any class that depends on IUrlHelper such as custom UrlHelper classes. Cheers


Marcel said...

Thank you, this was a great help (I was dreading having to go with Hanselman's solution and add a couple hundred lines of code just for the Url property).

Pham Thanh Cong said...

Thanks. That 's great :)

John Cleve said...

Thanks bro!

Ian Faithfull said...

Thanks. Your solution worked perfectly for me.

Post a Comment