【问题标题】:How to mock HttpContext.User如何模拟 HttpContext.User
【发布时间】:2017-03-06 23:02:45
【问题描述】:

我正在开发一个 Asp.net MVC 5 项目,我正在尝试设置一个模拟来返回控制器中的自定义主体。我已经搜索并尝试了建议的不同方法,但它们都不起作用。

我有一个 BaseController,我的所有控制器都继承自它。 BaseController 有一个 User 属性,它在 getter 中返回 HttpContext.User 。 HttpContext.user 在项目中调用时返回一个值,但在单元测试项目中调用时返回 null。

基础控制器

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

自定义主体

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

控制器

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]
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();
}

我正在使用 nUnit 和 Moq 创建模拟对象,但我不确定我做错了什么。我需要在 DocumentControl 中模拟 User.IsStore 的返回,以在我在测试中创建的自定义主体对象中返回 IsStore 的值。

【问题讨论】:

    标签: c# asp.net-mvc unit-testing moq httpcontext


    【解决方案1】:

    制作一个模拟 http 上下文

    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;
            }
        }
    }
    

    相应地安排测试。

    [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
    }
    

    不要模拟被测系统。模拟它的依赖。

    【讨论】:

    • 感谢 Nkosi,这很好用。感谢您的帮助和建议。
    【解决方案2】:

    如果您不直接依赖HttpContext,它会变得容易得多。创建一个IUserProvider 接口和一个依赖于HttpContext 的实现(例如HttpContextUserProvider),然后在您的测试中存根IUserProvider

    IUserProvider 应该通过依赖注入传递给您的控制器。

    【讨论】:

    • Stijn,感谢您对我的问题的回答。你有你建议的例子吗?我总是对尽可能简化/重构我的代码感兴趣。谢谢。
    • @EdmandLooi 我将在本周晚些时候尝试包含一个示例。如果我忘记了,请在一周左右再次联系我 :)
    • 来自微软文档:如何通过依赖注入添加HttpContext docs.microsoft.com/en-us/aspnet/core/fundamentals/…