c# - 单元测试依赖于 UserManager 和 RoleManager

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

我正在尝试对一些依赖于 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");
    }

Controller 和方法

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

最佳答案

如果你关注了我的博文,你应该知道 ApplicationUserManager 的构造函数是这样的:

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

并且您的 Controller 应该将用户管理器对象注入(inject)到构造函数中:

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 记录从 Controller 移到一个单独的类中 - 一个服务类,或者这可以是 ApplicationUserManager 的一部分,也可以是 QueryHandler 如果你愿意的话。无论你怎么调用它,它都不应该真正成为 Controller 的一部分。我认为这个方法实际上属于ApplicationUserManager。这样你的测试层就减少了一个,你只需要创建用户管理器类,而不是 Controller 。

鉴于 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");
}

将测试方法从 Controller 中移出的原因如下:

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

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

关于c# - 单元测试依赖于 UserManager 和 RoleManager,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35733185/

相关文章:

c# - 为什么路径由字符串表示而不是单独的类?

c# - WinForms 中的 WPF 装饰器

jquery - 通过 Jquery 获取 jsonString 为空

AngularJS - 在使用 moment.js 的 Controller 上进行单元测试

Python:模拟修补模块,无论它是从哪里导入的

c# - 来自 C# 的多个更新和插入

c# - 可选的 C# 参数问题

asp.net-mvc - Html.LabelFor 带有 HTML 的标签文本

c# - 在 asp.net mvc 中使用异步 Controller 有什么好处?

unit-testing - Angular 2 RC 如何对可观察的进行单元测试