单元测试依赖于UserManager和RoleManager

时间:2016-03-01 20:49:24

标签: 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");
    }

控制器和方法

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

2 个答案:

答案 0 :(得分:2)

如果您关注我的博文,您应该为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 。当前时间我很少使用模拟替代品并且更喜欢集成测试,甚至更喜欢使用DB,但这不是这个问题/讨论的目标。

因此,对于您的测试,您需要创建被测系统(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");
}

将测试方法从控制器中移出的原因如下:

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

我可以继续关注这个话题,但最好阅读DI book by Mark SeemanUnit Testing book by Roy Osherove

答案 1 :(得分:1)

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