单元测试依赖于用户管理器和角色管理器

本文关键字:管理器 角色 用户 依赖于 单元测试 | 更新日期: 2023-09-27 18:36:54

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

单元测试依赖于用户管理器和角色管理器

如果你关注了我的博客文章,你应该为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;
    }
}

现在开始你的测试 - 你需要一个模拟框架。几年前,我曾经在每次测试中都放置最小起订量,现在模拟偏好框架是 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测试起来并不简单(尽管可能)。此外,最好避免控制器与业务逻辑混合 - 帮助您避免意大利面条代码综合症。

我可以永远谈论这个话题,但最好阅读Mark Seeman的DI书和Roy Osherove的单元测试书。

你应该模拟用户管理器和角色管理器,并将它们传递给管理员控制器