-->

How to mock HttpContext.User

2020-04-02 11:37发布

问题:

I am working on a Asp.net MVC 5 project and I am trying to setup a mock to return a custom principal within a controller. I have search and tried different approach suggested but none of them works.

I have a BaseController which all my controllers inherit from. The BaseController has a User property which return HttpContext.User in the getter. The HttpContext.user returns a value when called within the project but return a null when call from a unit test project.

BaseController

public class BaseController : Controller
{
    protected virtual new CustomPrincipal User
    {
        get { return HttpContext.User as CustomPrincipal; }  ***<== Line with issue***
    }
}

Custom Principal

public class CustomPrincipal : IPrincipal, ICustomPrincipal
{
    public IIdentity Identity { get; private set; }

    public string UserId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public bool   IsStoreUser { get; set; }

    public CustomPrincipal(string username)
    {
        this.Identity = new GenericIdentity(username);
    }
}

Controller

public class DocumentsController : BaseController
    {
        public ViewResult ViewDocuments()
        {
            var userType = User.IsStoreUser ? UserType.StoreUser : UserType.Corporate;  ***<== User is null when calling from a unit test.***
        }
    }

Test Case

[Test]
public void ViewDocuments_WhenCalled_ShouldReturnViewModel()
{
    // Arrange
    var principal = new CustomPrincipal("2038786");
    principal.UserId = "2038786";
    principal.FirstName = "Test";
    principal.LastName = "User";
    principal.IsStoreUser = true;

    var _mockController = new Mock<DocumentsController>(new UnitOfWork(_context)) { CallBase = true };
        _mockController.Setup(u => u.User).Returns(principal);  ***<== Error - "Invalid setup on a non-virtual (overridable in VB) member: u => u.User"***

    // Act
    var result = _controller.ViewDocuments();
}

I'm using nUnit and Moq to create the mock object but I not sure what I'm doing wrong. I need to mock the return of User.IsStore in the DocumentControl to return the value of IsStore in the custom principal object i created in the test.

回答1:

Make a mock http context

private class MockHttpContext : HttpContextBase {
    private readonly IPrincipal user;

    public MockHttpContext(IPrincipal principal) {
        this.user = principal;
    }

    public override IPrincipal User {
        get {
            return user;
        }
        set {
            base.User = value;
        }
    }
}

Arrange test accordingly.

[Test]
public void ViewDocuments_WhenCalled_ShouldReturnViewModel() {
    // Arrange
    var principal = new CustomPrincipal("2038786");
    principal.UserId = "2038786";
    principal.FirstName = "Test";
    principal.LastName = "User";
    principal.IsStoreUser = true;

    var mockUoW = new Mock<IUnitOfWork>();
    //...setup UoW dependency if needed
    var controller = new DocumentsController(mockUoW.Object);
    controller.ControllerContext = new ControllerContext {
        Controller = controller,
        HttpContext = new MockHttpContext(principal)
    };

    // Act
    var result = controller.ViewDocuments();

    //Assert
    //...assertions
}

Don't mock system under test. Mock its dependencies.



回答2:

It gets a lot easier if you don't depend on HttpContext directly. Create an IUserProvider interface and an implementation that depends on HttpContext (e.g. HttpContextUserProvider), then stub the IUserProvider in your tests.

The IUserProvider should be passed along to your controller via dependency injection.