如何模拟HttpContext.User

20

我正在处理一个Asp.net MVC 5项目,试图设置一个模拟来在控制器中返回自定义的 principal。我已经搜索并尝试了不同的方法,但它们都没有奏效。

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

BaseController

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值。
2个回答

17
创建一个模拟的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,这很好用。感谢您的帮助和建议。 - Edmand Looi

6

如果你不直接依赖于HttpContext,那么这将变得更加容易。创建一个IUserProvider接口和一个依赖于HttpContext的实现(例如HttpContextUserProvider),然后在测试中使用IUserProvider来代替。

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


Stijn,感谢您对我的问题的回答。您有一个示例吗?我总是有兴趣在可能的情况下简化/重构我的代码。谢谢。 - Edmand Looi
@EdmandLooi 我会尝试在本周晚些时候提供一个例子。如果我忘记了,请在一周左右再次提醒我 :) - user247702
从微软文档:如何通过依赖注入添加 HttpContext https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-6.0#use-httpcontext-from-custom-components - Benxamin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接