Mock Controller's User and Url Properties in ASP.NET Core MVC
Published:
Filed under: Vigil Journey
I have come to the point where I am building out the initial proof-of-concept for the WebAPI portion of this project. This gives me a place to continue testing out features, and see if how I envisioned things would work can actually work. One of the first parts of unit testing the controller was to mock any of the properties that I need to use that the web server would ordinarily wire up. In a Controller
that primarily entails the ControllerContext
, which would also wire the Controller.User
, Controller.Url
, and Controller.HttpContext
properties. So how do I do that?
Mocking Controller.Url
I use Moq as my mocking framework, though this could be done with any library. The Url
property returns an IUrlHelper
, which makes it easy to mock.
namespace Microsoft.AspNetCore.Mvc
{
public interface IUrlHelper
{
ActionContext ActionContext { get; }
string Action(UrlActionContext actionContext);
string Content(string contentPath);
bool IsLocalUrl(string url);
string Link(string routeName, object values);
string RouteUrl(UrlRouteContext routeContext);
}
}
The Content(string)
, IsLocalUrl(string)
, and Link(string, object)
are all trivial to overload. The piece that I stumbled across was when I wanted to mock Url.Action(string, object)
, which is an extension method in the UrlHelperExtensions
class. Moq is unable to mock extension methods. However, because AspNetCore
is open source, I could happily go look at the source and see what the extension method was doing behind the scenes. All of the various extension methods just pass controll to the last (and longest) extension method, which calls Url.Action(UrlActionContext)
. They all pass a null
for values not collected.
public static string Action(
this IUrlHelper helper,
string action,
string controller,
object values,
string protocol,
string host,
string fragment)
{
if (helper == null)
{
throw new ArgumentNullException(nameof(helper));
}
return helper.Action(new UrlActionContext()
{
Action = action,
Controller = controller,
Host = host,
Values = values,
Protocol = protocol,
Fragment = fragment
});
}
This all means that in order to "mock" the extension methods, I just need to provide a mock for the IUrlHelper.Action(UrlActionContext)
method.
[Fact]
public void Create_With_ValidCommand_IsPublished_AndReturnsAcceptedResult_WithLocation()
{
CreatePatron cmd = new CreatePatron("Patron Web Test", TestHelper.Now)
{
DisplayName = "Patron Display Name",
IsAnonymous = false,
PatronType = "Test Patron"
};
var mockUrlHelper = new Mock<IUrlHelper>(MockBehavior.Strict);
Expression<Func<IUrlHelper, string>> urlSetup
= url => url.Action(It.Is<UrlActionContext>(uac => uac.Action == "Get" && GetId(uac.Values) != cmd.Id));
mockUrlHelper.Setup(urlSetup).Returns("a/mock/url/for/testing").Verifiable();
var controller = new PatronController()
{
Url = mockUrlHelper.Object
};
AcceptedResult result = controller.Create(cmd) as AcceptedResult;
Assert.NotNull(result);
mockUrlHelper.Verify(urlSetup, Times.Once());
Assert.Equal("a/mock/url/for/testing", result.Location);
}
private Guid? GetId(object values)
{
return values?.GetType().GetProperty("id")?.GetValue(values, null) as Guid?;
}
On my initial attempt, I was having the Url mock return an actual Url. However, I realized that since the string that returns from this shouldn't have any effect other than being a string, it should be some meaningless value. This code is not attempting to mock the routing or url creation based on the values sent to the IUrlHelper
. All this unit test is doing, and all it should be doning, is validating that the controller is asking for a string that comes from passing in the "Get" action and an object with a property of "id" that is a Guid
.
Mocking the UrlHelperExtensions.RouteUrl(this IUrlHelper, ...)
extension methods is done the same way, but by mocking the IUrlHelper.RouteUrl(UrlRouteContext)
method. The RouteUrl
extension methods also just pass along their values to a new instance of UrlRouteContext
and then call the interface's method.
Mocking Controller.User.Identity.Name
Mocking the User property on a Controller can be a little more difficult to figure out, because the ControllerBase.User
property is read-only; User
gets its value from the HttpContext.User
property. The Request
and Response
properties on the ControllerBase
also come from the HttpContext
. Fortuitously, ControllerBase.HttpContext
is settable and HttpContext
is an abstract class. Therefore, a simple stub of the User
property on a mock of the HttpContext
takes care of whatever needs there are for accessing the claims information.
Since the most common usage for this is that I am trying to access the user's name, I created a quick private method that goes inside of my test class. Moq takes care of walking the expression tree and creating the necessary mocks of the ClaimsPrincipal
and the IIdentity
properties.
private void SetupUser(Controller controller, string username)
{
var mockContext = new Mock<HttpContext>(MockBehavior.Strict);
mockContext.SetupGet(hc => hc.User.Identity.Name).Returns(username);
controller.ControllerContext = new ControllerContext()
{
HttpContext = mockContext.Object
};
}