c# – 单元测试依赖于UserManager的控制器的最佳实践?
我有一个带有以下签名的控制器:
[Route("api/[controller]")] [ApiController] public class UsersController : ControllerBase { private ILogger<UsersController> _logger; private readonly UserManager<IdentityUser> _usermanager; public UsersController(ILogger<UsersController> logger,UserManager<IdentityUser> usermanager) { _usermanager = usermanager; _logger = logger; } [HttpGet("{_uniqueid}")] public async Task<ObjectResult> GetUser(string _uniqueid) { //Retrieve the object try { var user = await _usermanager.FindByIdAsync(uniqueid); var model = JsonConvert.DeserializeObject<GetUserModel>(user.ToString()); return new ObjectResult(JsonConvert.SerializeObject(model)); } catch(CustomIdentityNotFoundException e) { return new BadRequestObjectResult(("User not found: {0}",e.Message)); } } } 现在我的单元测试看起来像这样: public class UsersUnitTests { public UsersController _usersController; private UserManager<IdentityUser> _userManager; public UsersUnitTests() { _userManager = new MoqUserManager<IdentityUser>(); _usersController = new UsersController((new Mock<ILogger<UsersController>>()).Object,_userManager); } [Fact] public async Task GetUser_ReturnsOkObjectResult_WhenModelStateIsValid() { //Setup //Test ObjectResult response = await _usersController.GetUser("realuser"); //Assert //Should receive 200 and user data content body response.StatusCode.Should().Be((int)System.Net.HttpStatusCode.OK); response.Value.Should().NotBeNull(); } } 以及Moq的课程: public class MoqUserManager<T> : UserManager<IdentityUser> { public MoqUserManager(IUserStore<IdentityUser> store,IOptions<IdentityOptions> optionsAccessor,IPasswordHasher<IdentityUser> passwordHasher,IEnumerable<IUserValidator<IdentityUser>> userValidators,IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators,ILookupNormalizer keyNormalizer,IdentityErrorDescriber errors,IServiceProvider services,ILogger<UserManager<IdentityUser>> logger) : base(store,optionsAccessor,passwordHasher,userValidators,passwordValidators,keyNormalizer,errors,services,logger) { } public MoqUserManager() : base((new MoqUserStore().Store),new Mock<IOptions<IdentityOptions>>().Object,new Mock<IPasswordHasher<IdentityUser>>().Object,new Mock<IEnumerable<IUserValidator<IdentityUser>>>().Object,new Mock<IEnumerable<IPasswordValidator<IdentityUser>>>().Object,new Mock<ILookupNormalizer>().Object,new Mock<IdentityErrorDescriber>().Object,new Mock<IServiceProvider>().Object,new Mock<ILogger<UserManager<IdentityUser>>>().Object) { } } public class MoqUserStore : IdentityUserStore { private Mock<IdentityUserStore> _store; public MoqUserStore() :base(new Mock<IdentityDbContext>().Object,new Mock<ILogger<IdentityUserStore>>().Object,null) { _store = new Mock<IdentityUserStore>(new Mock<IdentityDbContext>().Object,null); _store.Setup(x => x.FindByIdAsync("realuser",default(CancellationToken))).Returns(Task.Run(() => new IdentityUser("realuser"))); _store.Setup(x => x.FindByIdAsync("notrealuser",default(CancellationToken))).Throws(new CustomIdentityNotFoundException()); _store.Setup(x => x.CreateAsync(new IdentityUser("realuser"),default(CancellationToken))).Returns(Task.Run(() => IdentityResult.Success)); } public IdentityUserStore Store { get => _store.Object; } } 调用MoqUserManager构造函数时,我将引用未设置为对象错误的实例. 我的问题是:对于依赖于UserManager和/或SignInManager的这些类型的控制器进行单元测试,以及模拟UserStore依赖关系的简单可重复方法是什么,最好的做法是什么(我会满足于工作但是很难找到天堂) 解决方法
我想到了DI模型和我的控制器的依赖关系.我只需要UserManager中的一些方法,所以我理论上从UsersController中删除对UserManager的依赖,并将其替换为实现UserManager所需的相同签名的一些接口.让我们调用IMYUserManager接口:
public interface IMYUserManager { Task<IdentityUser> FindByIdAsync(string uniqueid); Task<IdentityResult> CreateAsync(IdentityUser IdentityUser); Task<IdentityResult> UpdateAsync(IdentityUser IdentityUser); Task<IdentityResult> DeleteAsync(IdentityUser result); } 接下来,我需要创建一个既来自UserManager又实现IMYUserManager的类.这里的想法是从接口实现方法将简单地成为派生类的覆盖,这样我绕过FindByIdAsync(和其余的)被标记为扩展方法并需要包装在静态类中.这是MyUserManager: public class MYUserManager : UserManager<IdentityUser>,IMYUserManager { public MYUserManager(IUserStore<IdentityUser> store,logger) { } public override Task<IdentityUser> FindByIdAsync(string userId) { return base.FindByIdAsync(userId); } //Removed other overridden methods for brevity; They also call the base class method } 快到家.接下来,我自然更新了UsersController以使用IMYUserManager接口: [Route("api/[controller]")] [ApiController] public class UsersController : ControllerBase { private ILogger<UsersController> _logger; private readonly IMYUserManager _usermanager; public UsersController(ILogger<UsersController> logger,IMYUserManager usermanager) { _usermanager = usermanager; _logger = logger; } } 而且,自然之后我必须为所有想要享用盛宴的人提供服务容器的依赖: public void ConfigureServices(IServiceCollection services) { services.AddScoped<IMYUserManager,MYUserManager>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } 最后,在验证实际构建之后,我更新了测试类: public class UsersControllerTests { public UsersController _usersController; private Mock<IMYUserManager> _userManager; public UsersControllerTests() { _userManager = new Mock<IMYUserManager>(); _usersController = new UsersController((new Mock<ILogger<UsersController>> ()).Object,_userManager.Object); } [Fact] public async Task GetUser_ReturnsOkObjectResult_WhenModelStateIsValid() { //Setup _userManager.Setup(x => x.FindByIdAsync("realuser")) .Returns(Task.Run(() => new IdentityUser("realuser","realuser1"))); _usersController.ModelState.Clear(); //Test ObjectResult response = await _usersController.GetUser("realuser"); //Assert //Should receive 200 and user data content body response.StatusCode.Should().Be((int)System.Net.HttpStatusCode.OK); response.Value.Should().NotBeNull(); } } 什么使这成为一个好的解决方案 几件事: 从UsersController中删除对UserManager的依赖性与DI模型一致.抽象出依赖关系(因此抽象出扩展方法之类的实现细节)并使它们不仅可以被模拟,而且可用于整个IServiceCollection意味着当我需要为用户管理器实现另一个方法时,我只有3个非常简单的步骤: >将方法签名添加到IMYUserManager 我可能会重新审视服务的范围,我选择AddScoped()来证明这个概念,但性能和业务需求将选择是否保持不变. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- objective-c – 如何将xcdatamodel文件转换为xcdatamodeld文
- ruby-on-rails – Rails Time.now,显示时间服务器启动而非实
- c# – .NET CIL Call或CallVirt?
- oracle – 如何避免合并时出现ORA-3814错误?
- C++复制构造函数,引用,指针,new
- Oracle用户、角色、权限简述
- c# – DataGridView行的背景颜色不变
- c – 使用SSE / AVX的整数点积?
- ruby-on-rails – 你如何解决? formtastic:label_method不
- c# – 如何在win表单项目中创建用户控件的dll?