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.

3
  • 1
    Not worth an answer on its own, so I'll point to a similar answer: bit.ly/aSJ0a Commented Sep 2, 2009 at 13:35
  • 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. Commented Sep 2, 2009 at 14:25
  • See the update in question... Commented Sep 2, 2009 at 14:27

6 Answers 6

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.

Sign up to request clarification or add additional context in comments.

1 Comment

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.
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));

3 Comments

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.
Do you know how we could achieve this with MVC4 Darin? As I'm trying to follow your code example but can't call RegisterRoutes
Tyler: RegisterRoutes is a convention, use MvcApplication.RouteTable.Routes.Add(...)
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).

Comments

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");

1 Comment

+1 bazillion for using FakeItEasy !! But ... the Action method is not virtual ... so how can you fake/intercept that?
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);

1 Comment

this didn't work
0

I've chased this one too for a while, and after consistently failing to get mocks like
Url.Action("MyMethod")).Returns("/MyController/MyMethod");
picked up, here's what eventually worked for me:

// Preceeded by "Url = Substitute.For<IUrlHelper>();"
Url
    .Action(Arg.Is<UrlActionContext>(ctx => ctx.Action == "MyMethod"))
    .Returns("/MyController/MyMethod");

invocated like so:

Url.Action(nameof(MyMethod))

We use NSubstitute, but this should apply for other mocking tools as well.

What eventually put me on the right track was the following test assert:

Url.Received().Action("MyMethod");

where the mock reported it was expecting an UrlActionContext, not one of the more obvious parameter overloads.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.