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
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