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:
[Authorize]
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)
{
base.Initialize(requestContext);
Url = new UrlHelperAdaptor(base.Url);
}
Now, the controller method is fully testable.
[Test]
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
result.Should().Not.Be.Null();
}
Using this approach can help you test any class that depends on IUrlHelper such as custom UrlHelper classes.
Cheers
5:59 PM
Unknown
4 comments:
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).
Thanks. That 's great :)
Thanks bro!
Thanks. Your solution worked perfectly for me.
Post a Comment