ASP.NET MVC: Mock controller.Url.Action
Asked Answered
E

5

26

Urls for menus in my ASP.NET MVC apps are generated from controller/actions. So, they call

controller.Url.Action(action, controller)

Now, how do I make this work in unit tests? I use MVCContrib successfully with

var controller = new TestControllerBuilder().CreateController<OrdersController>();

but whatever I try to do with it I get controller.Url.Action(action, controller) failing with NullReferenceException because Url == null.

Update: it's not about how to intercept HttpContext. I did this in several ways, using MVCContrib, Scott Hanselman's example of faking, and also the one from http://stephenwalther.com/blog/archive/2008/07/01/asp-net-mvc-tip-12-faking-the-controller-context.aspx. This doesn't help me because I need to know WHAT values to fake... is it ApplicationPath? How do I set it up? Does it need to match the called controller/action? That is, how do Url.Action works and how do I satisfy it?

Also, I know I can do IUrlActionAbstraction and go with it... but I'm not sure I want to do this. After all, I have MVCContrib/Mock full power and why do I need another abstraction.

Etui answered 2/9, 2009 at 13:11 Comment(3)
Not worth an answer on its own, so I'll point to a similar answer: bit.ly/aSJ0aSpeos
Yes I used that link and it didn't work. Actually I tried both Scott's version and MVCContrib. What I try to understand is what values do I need to setup? What does Url.Action() actually use? I.e. on the link you provided there's Moq version that setup a LOT of variables... are ALL of them necessary? I tried all of them without luck.Etui
See the update in question...Etui
K
22

Here's how you could mock UrlHelper using MvcContrib's TestControllerBuilder:

var routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
HomeController controller = CreateController<HomeController>();

controller.HttpContext.Response
    .Stub(x => x.ApplyAppPathModifier("/Home/About"))
    .Return("/Home/About");

controller.Url = new UrlHelper(
    new RequestContext(
        controller.HttpContext, new RouteData()
    ), 
    routes
);
var url = controller.Url.Action("About", "Home");
Assert.IsFalse(string.IsNullOrEmpty(url));
Kailakaile answered 3/9, 2009 at 13:55 Comment(3)
Oops... It was misleading to see in MSDN help for Controller.Url "Gets the URL helper object"... I didn't even verified if it has setter.Etui
Do you know how we could achieve this with MVC4 Darin? As I'm trying to follow your code example but can't call RegisterRoutesLaurentium
Tyler: RegisterRoutes is a convention, use MvcApplication.RouteTable.Routes.Add(...)Schwing
C
28

A cleaner way to do this is just use Moq(or any other framework you like) to Mock UrlHelper itself

var controller = new OrdersController();
var UrlHelperMock = new Mock<UrlHelper>();

controller.Url = UrlHelperMock.Object;

UrlHelperMock.Setup(x => x.Action("Action", "Controller", new {parem = "test"})).Returns("testUrl");

var url = controller.Url.Action("Action", "Controller", new {parem = "test"});
assert.areEqual("/Controller/Action/?parem=test",url);

clean and simple.

Catalepsy answered 23/11, 2015 at 17:43 Comment(1)
Doesn't work for me, I get: Can not instantiate proxy of class: Microsoft.AspNetCore.Mvc.Routing.UrlHelper because you can't mock the object itself, you need to mock the interface. But then, this method is not testable anymore because it is an extension method.Noach
K
22

Here's how you could mock UrlHelper using MvcContrib's TestControllerBuilder:

var routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
HomeController controller = CreateController<HomeController>();

controller.HttpContext.Response
    .Stub(x => x.ApplyAppPathModifier("/Home/About"))
    .Return("/Home/About");

controller.Url = new UrlHelper(
    new RequestContext(
        controller.HttpContext, new RouteData()
    ), 
    routes
);
var url = controller.Url.Action("About", "Home");
Assert.IsFalse(string.IsNullOrEmpty(url));
Kailakaile answered 3/9, 2009 at 13:55 Comment(3)
Oops... It was misleading to see in MSDN help for Controller.Url "Gets the URL helper object"... I didn't even verified if it has setter.Etui
Do you know how we could achieve this with MVC4 Darin? As I'm trying to follow your code example but can't call RegisterRoutesLaurentium
Tyler: RegisterRoutes is a convention, use MvcApplication.RouteTable.Routes.Add(...)Schwing
D
5

If you're using Moq (and not MvcContrib's TestControllerBuilder), you can mock out the context, similar to @DarianDimitrov's answer:

var controller = new OrdersController();
var context = new Mock<System.Web.HttpContextBase>().Object;

controller.Url = new UrlHelper(
    new RequestContext(context, new RouteData()),
    new RouteCollection()
);

This doesn't set the controller.HttpContext property, but it does allow Url.Action to execute (and return an empty string -- no mocking required).

Decalcify answered 12/11, 2015 at 17:51 Comment(0)
P
4

Fake it easy works nicely:

 var fakeUrlHelper = A.Fake<UrlHelper>();
        controller.Url = fakeUrlHelper;
        A.CallTo(() => fakeUrlHelper.Action(A<string>.Ignored, A<string>.Ignored))
            .Returns("/Action/Controller");
Persinger answered 26/7, 2014 at 8:46 Comment(1)
+1 bazillion for using FakeItEasy !! But ... the Action method is not virtual ... so how can you fake/intercept that?Association
G
0

Here is another way to solve the problem with NSubstitute. Hope, it helps someone.

// _accountController is the controller that we try to test
var urlHelper = Substitute.For<UrlHelper>();
urlHelper.Action(Arg.Any<string>(), Arg.Any<object>()).Returns("/test_Controller/test_action");

var context = Substitute.For<HttpContextBase>();
_accountController.Url = urlHelper;
_accountController.ControllerContext = new ControllerContext(context, new RouteData(), _accountController);
Gin answered 17/2, 2020 at 3:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.