C# 对依赖于UserManager的控制器进行单元测试的最佳实践<;TUser>;?

C# 对依赖于UserManager的控制器进行单元测试的最佳实践<;TUser>;?,c#,unit-testing,moq,xunit,asp.net-core-2.1,C#,Unit Testing,Moq,Xunit,Asp.net Core 2.1,我有一个具有以下签名的控制器: [Route("api/[controller]")] [ApiController] public class UsersController : ControllerBase { private ILogger<UsersController> _logger; private readonly UserManager<IdentityUser> _usermanager; public UsersContr

我有一个具有以下签名的控制器:

[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));
        }
    }
}
[路由(“api/[控制器]”)]
[ApiController]
公共类UsersController:ControllerBase
{
私人ILogger_记录器;
私有只读用户管理器_UserManager;
公共用户控制器(ILogger记录器、UserManager UserManager)
{
_usermanager=usermanager;
_记录器=记录器;
}
[HttpGet(“{u uniqueid}”)]
公共异步任务GetUser(字符串\u uniqueid)
{
//检索对象
尝试
{
var user=await\u usermanager.FindByIdAsync(uniqueid);
var model=JsonConvert.DeserializeObject(user.ToString());
返回新的ObjectResult(JsonConvert.SerializeObject(model));
}
捕获(CustomIdentityNotFounde异常)
{
返回新的BadRequestObjectResult((“未找到用户:{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();
    }
}
公共类usersunitests
{
公共用户控制器\u用户控制器;
私人用户管理器(UserManager);;
公共用户sunitests()
{
_userManager=新的MoqUserManager();
_usersController=newusersController((new Mock()).Object,\u userManager);
}
[事实]
公共异步任务GetUser\u ReturnsOkObjectResult\u WhenModelStateIsValid()
{
//设置
//试验
ObjectResult response=wait_usersController.GetUser(“realuser”);
//断言
//应接收200和用户数据内容体
response.StatusCode.Should().Be((int)System.Net.HttpStatusCode.OK);
response.Value.Should().NotBeNull();
}
}
以及最低起订量课程:

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, new Mock<ILogger<IdentityUserStore>>().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
{
公共MoqUserManager(IUserStore商店、IOptions选项访问器、,
IPasswordHasher密码hasher,IEnumerable用户验证程序,
IEnumerable PasswordValidator、ILookupNormalizer、keyNormalizer、,
IdentityErrorDescriptber错误,iSeries虚拟Provider服务,ILogger记录器)
:base(存储、选项访问器、密码哈希器、用户验证器、密码验证器、密钥规范化器、错误、服务、记录器)
{
}
公共MoqUserManager()
:base((新MoqUserStore().Store),新Mock().Object,
新建Mock().Object,新建Mock().Object,
新建Mock().Object,新建Mock().Object,
新建Mock().Object,新建Mock().Object,新建Mock().Object)
{
}
}
公共类MoqUserStore:IdentityUserStore
{
私人模拟商店;
公共Moquser商店()
:base(新Mock().Object,新Mock().Object,null)
{
_store=new Mock(new Mock().Object,new Mock().Object,null);
_store.Setup(x=>x.FindByIdAsync(“realuser”,默认值(CancellationToken))).Returns(Task.Run(()=>newidentityuser(“realuser”)));
_Setup(x=>x.FindByIdAsync(“notrealuser”,默认值(CancellationToken)).Throws(新的CustomIdentityNotFoundException());
_store.Setup(x=>x.CreateAsync(新的IdentityUser(“realuser”),默认值(CancellationToken)).Returns(Task.Run(()=>IdentityResult.Success));
}
public IdentityUserStore存储{get=>\u Store.Object;}
}
调用
MoqUserManager
构造函数时,我得到
引用未设置为对象实例的错误


我的问题是:对于依赖于
UserManager
和/或
SignInManager
的这些类型的控制器,单元测试的最佳实践是什么(我会满足于工作,但很讨厌),什么是模拟
UserStore
依赖关系的容易重复的方法?

我考虑了DI模型和控制器的依赖关系。我只需要
UserManager
中的一些方法,因此我从理论上考虑从
userscoontroller
中删除对
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);
}
public class MYUserManager : UserManager<IdentityUser>, IMYUserManager
{
    public MYUserManager(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 override Task<IdentityUser> FindByIdAsync(string userId)
    {
        return base.FindByIdAsync(userId);
    }
    //Removed other overridden methods for brevity; They also call the base class method
}
快到家了。接下来,我自然更新了
userscoontroller
以使用
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;
    }
}
[路由(“api/[控制器]”)]
[ApiController]
公共类UsersController:ControllerBase
{
私人ILogger_记录器;
私有只读IMYUserManager\u usermanager;
公共用户控制器(ILogger记录器、IMYUserManager
用户管理器)
{
_usermanager=usermanager;
_记录器=记录器;
}
}
当然,在这之后,我必须使服务容器能够为所有想要享用的人提供这种依赖性:

public void ConfigureServices(IServiceCollection services)
{

    services.AddScoped<IMYUserManager, MYUserManager>();


    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void配置服务(IServiceCollection服务)
{
services.addScope();
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();
    }
}
公共类用户控件测试
{
公共用户控制器\u用户控制器;
私人模拟用户管理器;
公共用户controllertests()
{
_userManager=newmock();
_usersController=新usersController((新模拟
()).Object,_userManager.Object);
}
[事实]
公共异步任务GetUser\u ReturnsOkObjectResult\u WhenModelStateIsValid()
{
//设置
_userManager.Setup(x=>x.FindByIdAsync(“realuser”))
.Returns(Task.Run(()=>newidentityuser(“realuser”,“realuser1”)));
_usersController.ModelState.Clear();
//试验
ObjectResult response=wait_usersController.GetUser(“realuser”);
//断言
//应接收200和用户数据内容体
response.StatusCode.Should().Be((int)System.Net.HttpStatusCode.OK);
response.Value.Should().NotBeNull();
}
}
是什么让这成为一个好的解决方案? 有几件事: