gpt4 book ai didi

c# - 单元测试依赖于 UserManager 的 Controller 的最佳实践?

转载 作者:行者123 更新时间:2023-11-30 12:38:21 26 4
gpt4 key购买 nike

我有一个具有以下签名的 Controller :

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

和最小起订量类:

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 构造函数时,出现 reference not set to an instance of an object 错误。

我的问题是:对于依赖于 UserManager 和/或 SignInManager,什么是模拟 UserStore 依赖项的易于重复的方法?

最佳答案

我考虑了 DI 模型和 Controller 的依赖项。我只需要 UserManager 中的一些方法,所以我从理论上讲从 UsersController 中删除对 UserManager 的依赖,并用一些接口(interface)替换它从 UserManager 实现我需要的相同签名。让我们调用该接口(interface) 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 的类。这里的想法是,从接口(interface)实现方法将简单地成为派生类的覆盖,这样我就可以绕过 FindByIdAsync (和其余部分)被标记为扩展方法并需要包装在静态类中.这是 MyUserManager:

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
}

快到家了。接下来,我很自然地更新了 UsersController 以使用 IMYUserManager 接口(interface):

[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 个非常简单的步骤,当我需要为用户管理器实现另一种方法:

  1. 将方法签名添加到IMYUserManager
  2. 重写方法,调用MYUserManager中的基类实现
  3. 在单元测试中模拟新的依赖

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

关于c# - 单元测试依赖于 UserManager<TUser> 的 Controller 的最佳实践?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53453232/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com