【问题标题】:Unit Test Relies on UserManager and RoleManager单元测试依赖于 UserManager 和 RoleManager
【发布时间】:2016-03-01 20:49:24
【问题描述】:

我正在尝试对依赖 UserManagerRoleManager 的一些方法进行单元测试,但遇到了一些困难。

我是否应该模拟UserManagerRoleManager,然后将其传递给AdminController?还是我应该首先访问AccountController 的默认SignIn 操作并进行身份验证。我不确定如何做这两种选择或最好的方法是什么。

当不验证/实例化管理器时,我在 UserManager 上得到 NullReferenceExceptions

我的测试

    [Test]
    public void MaxRole_SuperAdmin()
    {
        var adminController = new AdminController();
        var maxRole = adminController.GetMaxRole(SuperAdminUserId);

        Assert.AreEqual(maxRole, "Super Admin");
    }

控制器和方法

[Authorize(Roles = "APGame Admin, APGame Investigator")]
[RequireHttps]
public class AdminController : Controller
{

    private ApplicationUserManager _userManager;

    public ApplicationUserManager UserManager
    {
        get { return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>(); }
        private set { _userManager = value; }
    }

    private ApplicationRoleManager roleManager;

    public ApplicationRoleManager RoleManager
    {
        get
        {
            return roleManager ?? HttpContext.GetOwinContext().Get<ApplicationRoleManager>();
        }
        private set { roleManager = value; }
    }

    public string GetMaxRole(string userId)
    {
        IEnumerable<string> userRoles = UserManager.GetRoles(userId);

        string role = null;

        if (userRoles.Contains("APGame Admin"))
        {
            if (userRoles.Contains("TeVelde Group") && userRoles.Contains("Genomics Group"))
                role = "Super Admin";

            else role = "Admin";
        }

        else if (userRoles.Contains("APGame Investigator"))
        {
            role = "Investigator";
        }

        else if (userRoles.Contains("APGame User"))
        {
            role = "User";
        }

        else
        {
            //TODO: Log no role, HIGH
        }

        return role;
    }
}

【问题讨论】:

  • @PaulAbbott 在 UserManager 和 RoleManager 上模拟 HttpContext 有什么好处
  • 您从代码中的HttpContext 获取用户管理器和角色管理器。如果你模拟 HttpContext 来返回一个模拟的用户和角色管理器,你根本不需要修改你的代码。如果直接模拟用户和角色管理器,则需要更改代码以某种方式将它们注入控制器,而不是从 HttpContext 获取它们。

标签: c# asp.net-mvc unit-testing mocking asp.net-identity


【解决方案1】:

如果你关注了我的博文,你应该得到类似 ApplicationUserManager 的构造函数的东西:

public class ApplicationUserManager : UserManager<ApplicationUser>
{
    public ApplicationUserManager(IUserStore<ApplicationUser> store) : base(store)
    {
     // configuration-blah-blah
    }
}

并且您的控制器应该将用户管理器对象注入到构造函数中:

public class AdminController : Controller
{
    private readonly ApplicationUserManager userManager;
    public AdminController(ApplicationUserManager userManager)
    {
        this.userManager = userManager;
    }
}

现在进行测试 - 您需要一个模拟框架。几年前,我曾经将 MOQ 放在每个测试中,现在首选的模拟框架是 NSubstitue,因为语法更易读。目前我很少使用模拟替代品,更喜欢集成测试,甚至深入到数据库中,但这不是这个问题/讨论的目标。

因此,对于您的测试,您需要创建您的被测系统 (SUT) - AdminController

[Test]
public void MaxRole_SuperAdmin()
{
    var userStoreStub = NSubstitute.Substitute.For<IUserStore<ApplicationUser>>();
    var userManager = new ApplicationUserManager(userStoreStub);
    var sut = new AdminController(userManager);

    //TODO set up method substitutions on userStoreStub - see NSubstitute documentation

    var maxRole = sut.GetMaxRole(SuperAdminUserId);

    Assert.AreEqual(maxRole, "Super Admin");
}

但这是一个非常笨拙的测试。您正在尝试测试太深的东西。我建议您将 GetMaxRole 记录从控制器移到一个单独的类 - 一个服务类,或者这可以是 ApplicationUserManager 的一部分,或者如果您愿意,也可以是 QueryHandler。不管你怎么称呼它,它都不应该是控制器的一部分。我觉得这个方法其实属于ApplicationUserManager。这样你的测试层就减少了一层,你只需要创建用户管理器类,而不是控制器。

鉴于GetMaxRole() 方法是ApplicationUserManager 的一部分,您的测试将变得更小:

[Test]
public void MaxRole_SuperAdmin()
{
    var userStoreStub = NSubstitute.Substitute.For<IUserStore<ApplicationUser>>();
    var sut = new ApplicationUserManager(userStoreStub);

    //TODO set up method substitutions on userStoreStub - see NSubstitute documentation

    var maxRole = sut.GetMaxRole(SuperAdminUserId);

    Assert.AreEqual(maxRole, "Super Admin");
}

将被测方法移出控制器的原因如下:

  • 随着时间的推移,我发现控制器很容易随着添加/删除/替换新依赖项而经常更改。如果你有一些关于控制器的测试,你将不得不改变每个测试使用的依赖关系。
  • 如果你要在AdminController之外的其他地方使用GetMaxRole方法,你就有麻烦了。除了提供 HTTP(S) 端点之外,控制器不打算共享方法。
  • 您的方法的逻辑看起来像域逻辑或业务逻辑。这应该彻底测试。控制器不容易测试,因为它们处理 HTTP 请求和HttpContext,这不容易测试(尽管可能)。此外,最好避免将控制器内容与业务逻辑混合 - 帮助您避免意大利面条代码综合症。

我可以一直谈论这个话题,但最好阅读DI book by Mark SeemanUnit Testing book by Roy Osherove

【讨论】:

  • 我遇到的问题是调用 GetMaxRole 的 Post 操作也调用了 RoleManager,而我的 RoleManager 给出了异常,因为 RoleManager.Roles 不包含任何内容。此外,当我尝试将 GetMaxRole() 添加到 ApplictionUserManager 并仅测试 ApplicationManager 而不是控制器时,我收到错误 Store does not implement IUserRoleStore
【解决方案2】:

你应该模拟 UserManager 和 RoleManager 并将它们传递给 AdminController

【讨论】:

  • 我是这么想的,但我遇到了困难。关于如何模拟/实例化 UserManager 和 Role Manager,您有什么资源可以推荐吗?
  • 最好的办法是开始使用某种依赖注入(通过 nuget 可以使用大量文档齐全的库)。将所有外部依赖项注入到一个类中,使得单元测试非常非常容易。
  • 听起来不错,但我什至不知道从哪里开始。我将对依赖注入做一些研究,非常感谢!如果您对此有任何好的资源,将不胜感激!
  • @EitanK 你可以从我关于身份和 DI 的旧博文开始:tech.trailmax.info/2014/09/…,但也有其他资源:stackoverflow.com/questions/21927785/…talksharp.com/…
  • @trailmax 首先我要告诉你你的博客是多么的彻底和伟大,非常感谢!我已经实现了你的实现,并使得 Unity 也加载了 RoleManager。但是,我仍然不确定如何进行单元测试以及这如何使测试更容易。我尝试通过模拟 UserStore 和 RoleStore 进行测试,将它们传递给 ApplicationUserManager 和 ApplicationRoleManager 然后调用我的控制器。我遇到的问题是 Store 没有实现 IUserRoleStore。此外,我看到 ApplicationRoleManager.Roles 引发 NotSupported 异常
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-26
  • 2010-11-19
  • 1970-01-01
  • 2018-03-03
  • 2021-05-27
  • 2021-06-19
  • 2012-02-13
相关资源
最近更新 更多