测试使用User.Identity.Name的控制器操作

34

我有一个依赖于 User.Identity.Name 的操作,用于获取当前用户的用户名以获取他的订单列表:

public ActionResult XLineas()
    {
        ViewData["Filtre"] = _options.Filtre;
        ViewData["NomesPendents"] = _options.NomesPendents;
        return View(_repository.ObteLiniesPedido(User.Identity.Name,_options.Filtre,_options.NomesPendents));
    }

现在我正在尝试为此编写单元测试,但我卡在如何为 User.Identity.Name 提供 Mock 上。如果我按照当前的方式运行测试(没有 User 的 Mock ...),我会得到一个 Null.. 异常。

哪种方法是正确的?我想我的 Action 代码不适合进行单元测试。

2个回答

67

您可以使用这段代码

public SomeController CreateControllerForUser(string userName) 
{
    var mock = new Mock<ControllerContext>();
    mock.SetupGet(p => p.HttpContext.User.Identity.Name).Returns(userName);
    mock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);

    var controller = new SomeController();
    controller.ControllerContext = mock.Object;

    return controller;
}

它使用了Moq模拟框架,但你当然可以使用任何你喜欢的东西。


对于像我一样想知道的其他人:这个代码示例使用Moq - Joel Malone
3
在WebAPI中,我这样做:MyController controller = new MyController(); controller.User = new GenericPrincipal(new GenericIdentity(username, "Passport"), new[] { "tester" }); - Ravi
@Ravi 怎么办?Controller.User不可设置(虽然在过去的2.5年中可能已经发生了变化) - Wolfzoon
1
@Wolfzoon 我必须诚实地说,我不再使用DotNet了。我不记得回答过这个问题 :-)。请原谅我。 - Ravi

21
更好的做法是将一个字符串参数userName(或者一个IPrincipal参数user,如果您需要更多信息而不仅仅是名称)传递给ActionMethod,并将其“注入”到使用ActionFilterAttribute的普通请求中。当您进行测试时,只需提供自己的模拟对象,因为动作过滤器的代码通常不会运行(除非您有特定的需求...)。
Kazi Manzur Rashid在一篇优秀的博客文章中详细描述了这一点,其中第7点阐述了此方法。

1
我也是。另一方面,仅为了访问当前用户的用户名 - 而仅此而已 - 而嘲笑IPrincipal接口,就有点过头了... :) - Tomas Aschan
当传递IPrincipal或IIdentity对象时,您需要使用接口和包装器。仅使用ActionFilter将导致错误,指出您无法实现接口。 - Cavyn VonDeylen
Kazi Manzur Rashid的文章提供了一个很好的起点。我更喜欢修改代码,如果用户未经授权,则立即抛出InvalidOperationException异常。还要注意,您应该为ActionFilterAttribute编写单元测试,并且至少需要模拟这些东西一次。 - jakejgordon
@rob:感谢您让我意识到这个问题。这篇文章相当古老(当我在7年前写下这个答案时,它并不是全新的...),我也找不到任何存档的地方。然而,自那时以来,ASP.NET领域发生了很多变化,所以我相信有其他文章描述了类似问题的新解决方案。 - Tomas Aschan
@rob:我在网络档案馆中找到了这篇文章,链接已更新:D - Tomas Aschan
显示剩余2条评论

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