C# 如何使用ASP.NET Identity 3对ASP.NET 5帐户控制器进行单元测试

C# 如何使用ASP.NET Identity 3对ASP.NET 5帐户控制器进行单元测试,c#,asp.net,asp.net-mvc,unit-testing,C#,Asp.net,Asp.net Mvc,Unit Testing,我想单元测试使用ASP.NET Identity 3的ASP.NET 5帐户控制器,但我不确定最好的方法是什么。我的帐户控制器正在使用UserManager,正如我所看到的,这个UserManager类没有一个可以用来模拟它的接口,我已经检查了源代码。我认为的一种方法是将其包装到另一个实现接口的类中,然后使用该类进行模拟。这是使account controller单元可测试的唯一方法还是其他更好的方法。请注意,我指的不是需要使用数据库的集成测试。这是我必须要做的,才能使它工作。我完全抽象了接口后

我想单元测试使用ASP.NET Identity 3的ASP.NET 5帐户控制器,但我不确定最好的方法是什么。我的帐户控制器正在使用UserManager,正如我所看到的,这个UserManager类没有一个可以用来模拟它的接口,我已经检查了源代码。我认为的一种方法是将其包装到另一个实现接口的类中,然后使用该类进行模拟。这是使account controller单元可测试的唯一方法还是其他更好的方法。请注意,我指的不是需要使用数据库的集成测试。

这是我必须要做的,才能使它工作。我完全抽象了接口后面的身份模型,使其持久化


我创建了一些默认类,它们基本上包装了标识模型,允许我将紧密耦合分离到标识3,从而允许模拟并使控制器单元可测试。

实际上,有一个工具,它不仅允许模拟接口。您几乎可以模拟和测试任何东西,而无需创建大量包装器或接口等。

有两种方法可以做到这一点

模拟框架
UserManager
不是密封类。您可以使用现有的框架,如NSubstitute、mock等,将虚拟用户管理器注入到调用代码中。大多数框架(据我所知)都支持在mock中重写非密封类的行为,只要该方法被标记为
virtual
,而
UserManager
上的几乎所有这些方法都有这样的标记。下面是一个如何使用NSubstitute(我选择的框架)的示例

正如您所指出的,该类没有实现
IUserManager
接口,但没有密封,因此您可以创建自己的实现。您的实现可以实现一个接口,该接口可以在原始的
UserManager
类上指定现有方法,然后您可以使用来实现该接口

示例方法签名,然后是您的实现

用户管理器上的方法

公共虚拟任务CreateIdentityAsync(用户,字符串身份验证类型)
方法(在本例中为强类型)

Task CreateIdentityAsync(MyUserType用户,string authenticationType);
具体类上的实现(本例中为强类型)

异步任务IMyUserTypeManager.CreateIdentityAsync(MyUserType用户,字符串身份验证类型) { return wait this.CreateIdentityAsync(用户,authenticationType); } 使用您的控制器创建模拟与上面第一个示例中所示的相同

这种方法的好处是:

  • 现在,您可以更好地从实现代码中抽象出使用ASP.NET标识框架的事实。这将使将来更容易更新和/或更改ASP.NET标识
  • 您现在可以通过调用代码引用接口而不是具体的类。这将使单元测试更容易,因为您可以更容易地为这些接口创建模拟/替换

  • 这种方法的缺点是您必须创建和维护身份代码的代理,但通常您实际使用/调用的方法数量相当有限,因此不需要做更多的工作。

    实际上,这只是一种方法。这就是为什么这些测试被称为“单元测试”。单元意味着测试单个单元,而不是单元之间的集成。您正在实现的登录是一个单独的单元,标识和身份验证-另一个单元。
    var uManager = NSubstitute.Substitute.For<UserManager<MyUserType, int>>();
    var result = new ClaimsIdentity();
    uManager.CreateIdentityAsync(NSubstitute.Arg.Any<MyUserType>(), "login").Returns(Task.FromResult(result));
    var controller = new MyAuthController(uManager);
    controller.DoSomething();
    
    public class UserManager<TUser, TKey> : IDisposable where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
    
    public virtual Task<ClaimsIdentity> CreateIdentityAsync(TUser user, string authenticationType)
    
    Task<ClaimsIdentity> CreateIdentityAsync(MyUserType user, string authenticationType);
    
    async Task<ClaimsIdentity> IMyUserTypeManager.CreateIdentityAsync(MyUserType user, string authenticationType)
    {
        return await this.CreateIdentityAsync(user, authenticationType);
    }