gpt4 book ai didi

c# - 模拟的 UserManager 在测试中并没有真正按预期工作

转载 作者:行者123 更新时间:2023-11-28 21:37:23 30 4
gpt4 key购买 nike

我有一些服务负责在使用 UserManager 时更改用户密码并且一切正常,但是当我想编写一些测试时,它开始在方法 CheckPassword,它主要检查 current (old password) 是否正确

myService:
public async bool TryChangePassword(User user, string OldPassword, string NewPassword)
{
(...)

// it returns false
var checkOldPassword = await _userManager.CheckPasswordAsync(user, OldPassword);

if (!checkOldPassword)
{
return false;
}

var token = await _userManager.GeneratePasswordResetTokenAsync(user);

var result = await _userManager.ResetPasswordAsync(user, token, NewPassword);

return result.Succeeded;
}

myTests:

private readonly UserManager<User> _userManager;

[Fact]
public void password_change_attempt_1()
{
var service = new myService(_context, _userManager);

var user = new User("john");

var register = _userManager.CreateAsync(user, "123456");

_context.SaveChanges();

Assert.True(_context.Users.Any());

var result = service.TryChangePassword(user, "123456", "newPassword");
}

但由于某些原因它失败了:

var checkOldPassword = await _userManager.CheckPasswordAsync(user, OldPassword);

它返回 false,但如您所见,如果密码正确,则模拟用户管理器可能有问题

下面是我如何在 Tests 构造函数中创建 UserManager 的模拟

public Tests()
{
var o = new DbContextOptionsBuilder<Context>();
o.UseInMemoryDatabase(Guid.NewGuid().ToString());
_context = new Context(o.Options);
_context.Database.EnsureCreated();

var userStore = new MockUserStore(_context);
_userManager = new MockUserManager(userStore,
new Mock<IOptions<IdentityOptions>>().Object,
new Mock<IPasswordHasher<User>>().Object,
new IUserValidator<User>[0],
new IPasswordValidator<User>[0],
new Mock<ILookupNormalizer>().Object,
new Mock<IdentityErrorDescriber>().Object,
new Mock<IServiceProvider>().Object,
new Mock<ILogger<UserManager<User>>>().Object);
}

public class MockUserManager : UserManager<User>
{
public MockUserManager(IUserStore<User> store, IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<User> passwordHasher, IEnumerable<IUserValidator<User>> userValidators,
IEnumerable<IPasswordValidator<User>> passwordValidators, ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<User>> logger)
: base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
{
}

public override Task<IdentityResult> CreateAsync(User user)
{
this.Store.CreateAsync(user, new CancellationToken());
return Task.FromResult(IdentityResult.Success);
}
}
public class MockUserStore : IUserStore<User>, IUserPasswordStore<User>
{
public readonly Context _ctx;

public MockUserStore(Context ctx)
{
_ctx = ctx;
}
public Task<IdentityResult> CreateAsync(User user, CancellationToken cancellationToken)
{
_ctx.Users.Add(user);

return Task.FromResult(IdentityResult.Success);
}

public Task<IdentityResult> DeleteAsync(User user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}

public void Dispose()
{
throw new NotImplementedException();
}

public Task<User> FindByIdAsync(string userId, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}

public Task<User> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}

public Task<string> GetNormalizedUserNameAsync(User user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}

public Task<string> GetPasswordHashAsync(User user, CancellationToken cancellationToken)
{
return Task.FromResult<string>(user.PasswordHash);
}

public Task<string> GetUserIdAsync(User user, CancellationToken cancellationToken)
{
return Task.FromResult<string>(user.Id);
}

public Task<string> GetUserNameAsync(User user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}

public Task<bool> HasPasswordAsync(User user, CancellationToken cancellationToken)
{
return Task.FromResult<bool>(!String.IsNullOrEmpty(user.PasswordHash));
}

public Task SetNormalizedUserNameAsync(User user, string normalizedName, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}

public Task SetPasswordHashAsync(User user, string passwordHash, CancellationToken cancellationToken)
{
user.PasswordHash = passwordHash;
return Task.FromResult(0);
}

public Task SetUserNameAsync(User user, string userName, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}

public Task<IdentityResult> UpdateAsync(User user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}

我想它可以在这里修复:

public Task<IdentityResult> CreateAsync(User user, CancellationToken cancellationToken)
{
_ctx.Users.Add(user);

return Task.FromResult(IdentityResult.Success);
}

通过添加类似的东西

user.PasswordHash = generateHash(password)

但是我怎么知道 ASP.NET Core Identity 使用了多少次迭代?

https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/consumer-apis/password-hashing?view=aspnetcore-2.2

string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: password,
salt: salt,
prf: KeyDerivationPrf.HMACSHA1,
iterationCount: 10000,
numBytesRequested: 256 / 8));

最佳答案

您应该始终尽量避免使用如此深的模拟。使用您自己的界面隐藏具体实现。

public interface IMyUserManager
{
Task<bool> CheckPasswordAsync(User user, string oldPassword);
Task<string> GeneratePasswordResetTokenAsync(User user);
Task<IdentityResult> ResetPasswordAsync(User user, string token, string
newPassword);
}
}

其次,用内置的UserManager实现这个接口(interface)

public class MyUserManager : IMyUserManager 
{
private readonly UserManager<User> _userManager;

public MyUserManager(UserManager<User> userManager)
{
if (userManager is null)
{
throw new ArgumentNullException(nameof(userManager));
}
_userManager = userManager;
}

public Task<bool> CheckPasswordAsync(User user, string oldPassword)
{
return _userManager.CheckPasswordAsync(user, oldPassword);
}

public Task<string> GeneratePasswordResetTokenAsync(User user)
{
return _userManager.GeneratePasswordResetTokenAsync(user);
}

public Task<IdentityResult> ResetPasswordAsync(User user, string token,
string newPassword)
{
return _userManager.ResetPasswordAsync(user, token, newPassword);
}
}

然后,重写你的服务

        private readonly IMyUserManager _userManager;

public MyService(IMyUserManager userManager)
{
_userManager = userManager;
}
public async Task<bool> TryChangePassword(User user, string OldPassword, string NewPassword)
{
// it returns false
var checkOldPassword = await _userManager.CheckPasswordAsync(user, OldPassword);

if (!checkOldPassword)
{
return false;
}

var token = await _userManager.GeneratePasswordResetTokenAsync(user);

var result = await _userManager.ResetPasswordAsync(user, token, NewPassword);

return result.Succeeded;

最后,尽可能简单地编写测试用例。你周围的人在修复测试时永远不想去实现子类

[Fact]
public async Task password_change_attempt_1()
{
var mock = new Mock<IMyUserManager>();
MyService myService = new MyService(mock.Object);
mock.Setup(x => x.CheckPasswordAsync(It.IsAny<User>(), It.IsAny<string>()))
.Returns(Task.FromResult(true));
mock.Setup(x => x.ResetPasswordAsync(It.IsAny<User>(), It.IsAny<string>(),
It.IsAny<string>()))
.Returns(Task.FromResult(IdentityResult.Success));

var result =
await myService
.TryChangePassword(new User { Name = "Name", }, "OldPassword", "NewPassword");

Assert.Equal(result, true);
}

关于c# - 模拟的 UserManager 在测试中并没有真正按预期工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56835561/

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