我有一些服务,该服务负责在使用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 =等待_userManager.CheckPasswordAsync(user,OldPassword);
它返回false,但是如您所见,只要密码正确,嘲笑用户管理器可能有问题
这是如何在UserManager
构造函数中创建Tests'
的模拟物
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));
答案 0 :(得分:0)
您应该始终避免使用如此深的模拟。 用自己的界面隐藏具体的实现。
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实现此接口
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);
}