3

In my MVC project, I am building unit tests for a controller to return a view.

This controller's action result builds a viewmodel, and this viewmodel's constructor calls

HttpContext.Current.User.Identity.NameWithoutDomain().ToUpper();

Where NameWithoutDomain is an extension method of IIdentity.

Whenever I run it, I keep getting "the object cannot be found" and I'm curious how can I Mock this appropriately for unit testing?

Extension method:

public static class SystemWebExtension
{
    public static string NameWithoutDomain(this IIdentity identity)
    {
        return identity.Name.Split('\\').Last();
    }
}

Model constructor:

public Model()
{
    this.PreparedByUser = HttpContext.Current.User.Identity.NameWithoutDomain().ToUpper();
    this.AuthorizedBy = true;
    this.AuthorizedByUser = HttpContext.Current.User.Identity.NameWithoutDomain().ToUpper();
    this.IssueCredit = true;
    this.CreateUser = string.IsNullOrEmpty(this.CreateUser) ? Environment.UserName.ToLower() : this.CreateUser;
    this.CreateDate = this.CreateDate.HasValue ? this.CreateDate : DateTime.Now;
    this.UpdateUser = string.IsNullOrEmpty(this.CreateUser) ? null : Environment.UserName.ToLower();
    this.UpdateDate = this.CreateDate.HasValue ? DateTime.Now : (DateTime?) null;
}
Nkosi
  • 191,971
  • 29
  • 311
  • 378
DeeZeeUser
  • 41
  • 5
  • Have you read [this?](https://stackoverflow.com/questions/1452418/how-do-i-mock-the-httpcontext-in-asp-net-mvc-using-moq) – GoldenAge Feb 25 '19 at 16:15
  • You could create a wrapper around the HttpContext and mock the wrapper. More about wrapper pattern is [here](https://stackoverflow.com/questions/889160/what-is-a-wrapper-class). – Martin Staufcik Feb 25 '19 at 16:16

2 Answers2

0

Avoid coupling to HttpContext as it is not available when unit testing.

You should explicitly inject the necessary values into the model

public Model(IIdentity identity) {
    this.PreparedByUser = identity.NameWithoutDomain().ToUpper();
    this.AuthorizedBy = true;
    this.AuthorizedByUser = identity.NameWithoutDomain().ToUpper();
    this.IssueCredit = true;
    this.CreateUser = string.IsNullOrEmpty(this.CreateUser) ? Environment.UserName.ToLower() : this.CreateUser;
    this.CreateDate = this.CreateDate.HasValue ? this.CreateDate : DateTime.Now;
    this.UpdateUser = string.IsNullOrEmpty(this.CreateUser) ? null : Environment.UserName.ToLower();
    this.UpdateDate = this.CreateDate.HasValue ? DateTime.Now : (DateTime?) null;
}

Since using a controller, you would have access to the desired information in its properties

//...

var model = new Model(this.User.Identity);

return View(model);

And when testing, you can configure the controller via its controller context

//...
var username = "some username here"
var identity = new GenericIdentity(username, "");
identity.AddClaim(new Claim(ClaimTypes.Name, username));
var principal = new GenericPrincipal(identity, roles: new string[] { });
var user = new ClaimsPrincipal(principal);

var context = new Mock<HttpContextBase>();
context.Setup(_ => _.User).Returns(user);

//...

controller.ControllerContext = new ControllerContext(context.Object, new RouteData(),  controller );
Nkosi
  • 191,971
  • 29
  • 311
  • 378
0

You can't mock extension methods, you would have to mock what happens inside the extension method so that it returns the value you want.

To make sure that NameWithoutDomain returns what you want, you could mock IIdentity and make it return the username you want, like so:

var identityMock = new Mock<IIdentity>();
identityMock.SetupGet(x => x.Name).Returns("UsernameWithoutDomain");

If you want to test that NameWithoutDomain does what it's supposed to, which is splitting the string, then you write a separate test that calls this extension method.

As for HttpContext, you can just instantiate it before creating your controller, and pass it the mocked IIdentity, for example like this (tested with WebAPI2):

var identityMock = new Mock<IIdentity>();
identityMock.SetupGet(x => x.Name).Returns("UsernameWithoutDomain");

HttpContext.Current =
    new HttpContext(new HttpRequest(null, "http://test.com", null), new HttpResponse(null))
    {
        User = new GenericPrincipal(identityMock.Object, new[] {"Role1"})
    };

var myController = new AccountController();

Then when you create an instance of your controller for testing, HttpContext.Current.User.Identity.Name will return UsernameWithoutDomain. Happy testing! :-)

Shahin Dohan
  • 4,247
  • 1
  • 29
  • 46