在ASP.NET Core中模拟IPrincipal

Fel*_*lix 73 c# unit-testing xunit.net asp.net-core-mvc

我有一个ASP.NET MVC核心应用程序,我正在编写单元测试.其中一个操作方法使用用户名来实现某些功能:

SettingsViewModel svm = _context.MySettings(User.Identity.Name);
Run Code Online (Sandbox Code Playgroud)

这显然在单元测试中失败了.我环顾四周,所有建议都是从.NET 4.5到模拟HttpContext.我相信有更好的方法可以做到这一点.我试图注入IPrincipal,但它引发了一个错误; 我甚至试过这个(出于绝望,我想):

public IActionResult Index(IPrincipal principal = null) {
    IPrincipal user = principal ?? User;
    SettingsViewModel svm = _context.MySettings(user.Identity.Name);
    return View(svm);
}
Run Code Online (Sandbox Code Playgroud)

但这也引发了一个错误.无法在文档中找到任何内容......

pok*_*oke 137

通过控制器User访问HttpContext控制器.后者存储在ControllerContext.

替换用户的最简单方法是为构造用户分配不同的HttpContext.我们可以DefaultHttpContext为此目的使用,这样你就不必模拟一切:

var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
{
    new Claim(ClaimTypes.Name, "example name"),
    new Claim(ClaimTypes.NameIdentifier, "1"),
    new Claim("custom-claim", "example claim value"),
}, "mock"));

var controller = new SomeController(dependencies…);
controller.ControllerContext = new ControllerContext()
{
    HttpContext = new DefaultHttpContext() { User = user }
};
Run Code Online (Sandbox Code Playgroud)

  • 在我的例子中,它是`新的Claim(ClaimTypes.Name,"1")`以匹配`user.Identity.Name`的控制器使用; 但除此之外,这正是我想要实现的...... Danke schon! (7认同)

Nko*_*osi 15

在以前的版本中,您可以User直接在控制器上进行设置,这可以进行一些非常简单的单元测试.

如果您查看ControllerBase的源代码,您会注意到它User是从中提取的HttpContext.

/// <summary>
/// Gets or sets the <see cref="ClaimsPrincipal"/> for user associated with the executing action.
/// </summary>
public ClaimsPrincipal User
{
    get
    {
        return HttpContext?.User;
    }
}
Run Code Online (Sandbox Code Playgroud)

并且控制器访问HttpContext通道ControllerContext

/// <summary>
/// Gets the <see cref="Http.HttpContext"/> for the executing action.
/// </summary>
public HttpContext HttpContext
{
    get
    {
        return ControllerContext.HttpContext;
    }
}
Run Code Online (Sandbox Code Playgroud)

您会注意到这两个是只读属性.好消息是,ControllerContext财产允许设定它的价值,这将是你的方式.

所以目标是获得该对象.在Core中HttpContext是抽象的,因此模拟起来要容易得多.

假设有一个控制器

public class MyController : Controller {
    IMyContext _context;

    public MyController(IMyContext context) {
        _context = context;
    }

    public IActionResult Index() {
        SettingsViewModel svm = _context.MySettings(User.Identity.Name);
        return View(svm);
    }

    //...other code removed for brevity 
}
Run Code Online (Sandbox Code Playgroud)

使用Moq,测试看起来像这样

public void Given_User_Index_Should_Return_ViewResult_With_Model() {
    //Arrange 
    var username = "FakeUserName";
    var identity = new GenericIdentity(username, "");

    var mockPrincipal = new Mock<IPrincipal>();
    mockPrincipal.Setup(x => x.Identity).Returns(identity);
    mockPrincipal.Setup(x => x.IsInRole(It.IsAny<string>())).Returns(true);

    var mockHttpContext = new Mock<HttpContext>();
    mockHttpContext.Setup(m => m.User).Returns(mockPrincipal.Object);

    var model = new SettingsViewModel() {
        //...other code removed for brevity
    };

    var mockContext = new Mock<IMyContext>();
    mockContext.Setup(m => m.MySettings(username)).Returns(model);

    var controller = new MyController(mockContext.Object) {
        ControllerContext = new ControllerContext {
            HttpContext = mockHttpContext.Object
        }
    };

    //Act
    var viewResult = controller.Index() as ViewResult;

    //Assert
    Assert.IsNotNull(viewResult);
    Assert.IsNotNull(viewResult.Model);
    Assert.AreEqual(model, viewResult.Model);
}
Run Code Online (Sandbox Code Playgroud)


Cal*_*lin 5

还可以使用现有的类,并仅在需要时进行模拟。

var user = new Mock<ClaimsPrincipal>();
_controller.ControllerContext = new ControllerContext
{
    HttpContext = new DefaultHttpContext
    {
        User = user.Object
    }
};
Run Code Online (Sandbox Code Playgroud)