【发布时间】:2019-07-01 12:17:03
【问题描述】:
我有一些服务负责在使用 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 使用了多少次迭代?
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: password,
salt: salt,
prf: KeyDerivationPrf.HMACSHA1,
iterationCount: 10000,
numBytesRequested: 256 / 8));
【问题讨论】:
-
当你说“失败”时,失败是什么?例外?
-
@JamieRees "它返回 false,但如您所见,只要密码正确,可能是模拟用户管理器有问题"
-
你为什么要创建一个自定义的
UserManager实现?你已经模拟了所有的依赖,所以直接新建UserManager<TUser>。创建自定义实现会引入更多未经测试的代码,这可能会使您的其他测试失败,即使这些测试实际上没有任何问题。关键是尽可能使用真实的实现。
标签: c# testing asp.net-core mocking