简体   繁体   English

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

[英]How to unit test an ASP.NET 5 account controller using ASP.NET Identity 3

I would like to unit test an ASP.NET 5 account controller using ASP.NET Identity 3 but I am not sure what is the best way to do this. 我想使用ASP.NET Identity 3对ASP.NET 5帐户控制器进行单元测试 ,但我不确定执行此操作的最佳方法是什么。 My account controller is using the UserManager and as I can see this UserManager class does not have an interface that I can use to mock it, I have checked the source code. 我的帐户控制器正在使用UserManager,因为我可以看到这个UserManager类没有可用于模拟它的界面,我已经检查了源代码。 One way I thought would be be for me to wrap it into another class that implements an interface and then use that for mocking. 我认为的一种方法是将它包装到另一个实现接口的类中,然后将其用于模拟。 Is this the only way to make the account controller unit testable or there other better ways.Please note I am not referring to integration tests that require database use. 这是使帐户控制器单元可测试的唯一方法还是其他更好的方法。请注意,我不是指需要数据库使用的集成测试。

That's what I had to do to get it to work. 这就是我必须做的才能让它发挥作用。 I completely abstracted the Identity Model behind interfaces to make it persistence ignorant. 我完全抽象了接口背后的身份模型,使其持久性无知。

I created some default classes that basically wrapped the Identity Model that allowed me to separate the tight coupling to Identity 3, which allowed for mocking and making my controllers unit testable. 我创建了一些基本上包含Identity Model的默认类,它允许我将紧耦合分离到Identity 3,这允许模拟并使我的控​​制器单元可测试。

Actually, there is a Typemock Isolator tool, which allows to mock not only the interfaces. 实际上,有一个Typemock Isolator工具,它不仅可以模拟接口。 You can mock and test almost anything, without creating a huge amount of wrappers, or interfaces etc. 您可以模拟和测试几乎任何东西,而无需创建大量的包装器或接口等。

There are 2 ways to go about doing this. 有两种方法可以做到这一点。

Mocking Framework 模拟框架

UserManager is not a sealed class. UserManager不是密封类。 You can use existing frameworks like NSubstitute, Mocks, etc. to inject a dummy UserManager into your calling code. 您可以使用现有的框架(如NSubstitute,Mocks等)将虚拟UserManager注入您的调用代码。 Most of these frameworks (that I am aware of) support overriding behavior on non-sealed classes in the mock as long as the method has been marked as virtual which almost all these methods on UserManager have. 大多数这些框架(我都知道)支持在模拟中的非密封类上覆盖行为,只要该方法已被标记为virtual ,而UserManager上几乎所有这些方法都具有该方法。 Here is an example of how to do it with NSubstitute (my framework of choice). 这是一个如何使用NSubstitute(我选择的框架)的例子。

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();

Code Refactor 代码重构

This is a more involved approach but maybe better for the long run. 这是一种更复杂的方法,但从长远来看可能更好。

Here is the class signature of the UserManager ( version 2 of Identity framework but I imagine version 3 does not have any breaking changes on the class itself but it could be slightly different ). 这是UserManager的类签名( Identity框架的第2版,但我认为版本3对类本身没有任何重大更改,但可能略有不同 )。

public class UserManager<TUser, TKey> : IDisposable where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>

The class, as you noted, does not implement an IUserManager interface but is not sealed so you can create your own implementation. 正如您所指出的,该类未实现IUserManager接口,但未密封,因此您可以创建自己的实现。 Your implementation can implement an interface and this interface can specify existing methods on the original UserManager<TUser, TKey> class, you can then implement the interface using explicit implementation . 您的实现可以实现一个接口,并且此接口可以在原始UserManager<TUser, TKey>类上指定现有方法,然后您可以使用显式实现实现接口。

Example method signature and then your implementation. 示例方法签名然后执行。

Method on UserManager<TUser, TKey> UserManager<TUser, TKey>方法UserManager<TUser, TKey>

public virtual Task<ClaimsIdentity> CreateIdentityAsync(TUser user, string authenticationType)

Method on your interface (strongly typed in this example). 您的界面上的方法(在此示例中强类型)。

Task<ClaimsIdentity> CreateIdentityAsync(MyUserType user, string authenticationType);

Implementation on your concrete class (strongly typed in this example). 具体类的实现(在本例中强类型)。

async Task<ClaimsIdentity> IMyUserTypeManager.CreateIdentityAsync(MyUserType user, string authenticationType)
{
    return await this.CreateIdentityAsync(user, authenticationType);
}

Creating a mock with your controller would be the same as illustrated in the first example above. 使用控制器创建模拟将与上面第一个示例中说明的相同。

The benefit of this approach: 这种方法的好处:

  1. You have now better abstracted the fact that you are using the ASP.NET Identity framework from your implementing code. 您现在已经更好地抽象了您正在使用实现代码中的ASP.NET Identity框架这一事实。 This should make it easier to make updates and/or changes ASP.NET Identity in the future. 这样可以使将来更容易更新和/或更改ASP.NET标识。
  2. You can now reference interfaces instead of concrete classes by the calling code. 您现在可以通过调用代码引用接口而不是具体类。 This will make it easier to unit test as you can more easily create mocks/substitutes for these interfaces. 这样可以更轻松地进行单元测试,因为您可以更轻松地为这些接口创建模拟/替代。

The downside of this approach is you have to create and maintain a proxy to the Identity code but usually the number of methods that you actually use/call into is fairly limited so its not much more work. 这种方法的缺点是你必须创建和维护身份代码的代理,但通常你实际使用/调用的方法的数量是相当有限的,所以它没有多少工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM