我有一个带有以下签名的控制器:
[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
>重写方法并在MYUserManager中调用基类实现
>在单元测试中模拟新的依赖
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。