加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

c# – 单元测试依赖于UserManager的控制器的最佳实践?

发布时间:2020-12-15 22:53:38 所属栏目:百科 来源:网络整理
导读:我有一个带有以下签名的控制器: [Route("api/[controller]")][ApiController]public class UsersController : ControllerBase{ private ILoggerUsersController _logger; private readonly UserManagerIdentityUser _usermanager; public UsersController(I
我有一个带有以下签名的控制器:

[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中调用基类实现
>在单元测试中模拟新的依赖

我可能会重新审视服务的范围,我选择AddScoped()来证明这个概念,但性能和业务需求将选择是否保持不变.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读