Xunit 和 Mock 数据与最小起订量
Xunit and Mock data with Moq
我是单元测试的新手,任何人都可以建议如何使用 xUnit 和 Moq 测试下面的 public 方法 (CreateUser),谢谢!
public async Task<bool> CreateUser(UserDTO newUser)
{
newUser.CustomerId = _userResolverService.GetCustomerId();
if (await CheckUserExists(newUser)) return false;
var salt = GenerateSalt(10);
var passwordHash = GenerateHash(newUser.Password, salt);
await _usersRepository.AddAsync(new User()
{
Role = newUser.Role,
CretedOn = DateTime.Now,
CustomerId = newUser.CustomerId,
Email = newUser.Email,
FirstName = newUser.FirstName,
LastName = newUser.LastName,
PasswordHash = passwordHash,
Salt = salt,
UpdatedOn = DateTime.Now
});
return true;
}
下面是私有方法
检查用户是否仅存在 returns 现有用户数
private async Task<bool> CheckUserExists(UserDTO user)
{
var users = await _usersRepository.GetAllAsync();
var userCount = users.Count(u => u.Email == user.Email);
return userCount > 0;
}
哈希生成
private static string GenerateHash(string input, string salt)
{
var bytes = System.Text.Encoding.UTF8.GetBytes(input + salt);
var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(bytes);
return ByteArrayToString(hash);
}
盐世代
private static string GenerateSalt(int size)
{
var rng = RandomNumberGenerator.Create();
var buff = new byte[size];
rng.GetBytes(buff);
return Convert.ToBase64String(buff);
}
private static string ByteArrayToString(byte[] ba)
{
var hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
谢谢,
J
编辑 1
这是我目前所拥有的:
[Fact]
public async void CreateUser_True()
{
//arrange
var dataSource = new Mock<IRepository<User, int>>();
var userResolverService = new Mock<IUserResolverService>();
var tokenService = new Mock<ITokenService>();
var users = new List<User>();
users.Add(new User()
{
Email = "test@test.com",
CustomerId = 1
});
dataSource.Setup(m => m.GetAllAsync()).ReturnsAsync(users); // Error Here with converting async task to IEnumerable...
var accountService = new AccountService(dataSource.Object,userResolverService.Object,tokenService.Object);
//act
//assert
}
主要问题是我不知道如何模拟、设置私有方法 "CheckUserExists" 的行为,因为它正在调用 IRepository,它被模拟为 class,在这种情况下也是如此 return 我的 GetAllAsync 模拟设置来自这个私有方法?
编辑 2
public async Task<TEntity> AddAsync(TEntity entity)
{
if (entity == null)
{
throw new ArgumentNullException(nameof(entity));
}
_entities.Add(entity);
await _context.SaveChangesAsync();
return entity;
}
编辑 3
[Fact]
public async void CreateUser_True()
{
//arrange
var users = new List<User>();
users.Add(new User()
{
Email = "test@test.com",
CustomerId = 1
});
_dataSource.Setup(m => m.GetAllAsync()).ReturnsAsync(users);
_dataSource.Setup(m => m.AddAsync(It.IsAny<User>())).Returns<User>(Task.FromResult);
var accountService = new AccountService(_dataSource.Object, _userResolverService.Object, _tokenService.Object);
//act
var result = await accountService.CreateUser(new UserDTO()
{
Email = "truetest@test.com"
});
var updatedUsersList = await _dataSource.Object.GetAllAsync();
var usersCount = updatedUsersList.Count();
//assert
Assert.True(result);
Assert.Equal(2, usersCount);
}
由于被测试的方法是异步的,您需要设置所有异步依赖项以允许方法流完成。至于私有方法,您想设置该方法中使用的任何依赖项的行为,在本例中是用户存储库。
[Fact]
public async Task CreateUser_True() {
//arrange
var usersRepository = new Mock<IRepository<User, int>>();
var userResolverService = new Mock<IUserResolverService>();
var tokenService = new Mock<ITokenService>();
var user = new User() {
Email = "test@test.com",
CustomerId = 1
};
var users = new List<User>() { user };
usersRepository.Setup(_ => _.GetAllAsync()).ReturnsAsync(users);
usersRepository.Setup(_ => _.AddAsync(It.IsAny<User>()))
.Returns<User>(arg => Task.FromResult(arg)) //<-- returning the input value from task.
.Callback<User>(arg => users.Add(arg)); //<-- use call back to perform function
userResolverService.Setup(_ => _.GetCustomerId()).Returns(2);
var accountService = new AccountService(usersRepository.Object, userResolverService.Object, tokenService.Object);
//act
var actual = await accountService.CreateUser(new UserDto {
Email = "email@example.com",
Password = "monkey123",
//...other code removed for brevity
});
//assert
Assert.IsTrue(actual);
Assert.IsTrue(users.Count == 2);
Assert.IsTrue(users.Any(u => u.CustomerId == 2);
}
继续阅读 Moq Quickstart 以更好地了解如何使用模拟框架。
@sziszu 据我正确理解,您可以为 public 方法的单元测试设置模拟。这是解决方案。
我重写了你的代码,我的项目结构看起来像 visual studio project structure
这是我的代码片段
用户操作Class
public class UserOperation
{
#region Fields
private readonly IUserResolverService _userResolverService;
private readonly IUsersRepository _usersRepository;
#endregion
#region Constructor
public UserOperation(IUserResolverService userResolverService, IUsersRepository usersRepository)
{
_userResolverService = userResolverService;
_usersRepository = usersRepository;
}
#endregion
#region Public Methods
public async Task<bool> CreateUser(UserDTO newUser)
{
newUser.CustomerId = _userResolverService.GetCustomerId();
if (await CheckUserExists(newUser)) return false;
var salt = GenerateSalt(10);
var passwordHash = GenerateHash(newUser.Password, salt);
await _usersRepository.AddAsync(new User
{
Role = newUser.Role,
CretedOn = DateTime.Now,
CustomerId = newUser.CustomerId,
Email = newUser.Email,
FirstName = newUser.FirstName,
LastName = newUser.LastName,
PasswordHash = passwordHash,
Salt = salt,
UpdatedOn = DateTime.Now
});
return true;
}
#endregion
#region PrivateMethods
private async Task<bool> CheckUserExists(UserDTO user)
{
var users = await _usersRepository.GetAllAsync();
var userCount = users.Count(u => u.Email == user.Email);
return userCount > 0;
}
private static string GenerateHash(string input, string salt)
{
var bytes = Encoding.UTF8.GetBytes(input + salt);
var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(bytes);
return ByteArrayToString(hash);
}
private static string GenerateSalt(int size)
{
var rng = RandomNumberGenerator.Create();
var buff = new byte[size];
rng.GetBytes(buff);
return Convert.ToBase64String(buff);
}
private static string ByteArrayToString(byte[] ba)
{
var hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
#endregion
}
以下是我的存储库和服务。
public interface IUsersRepository
{
Task AddAsync(User user);
Task<ICollection<User>> GetAllAsync();
}
public class UsersRepository : IUsersRepository
{
public Task AddAsync(User user)
{
//Code for adding user
}
public Task<ICollection<User>> GetAllAsync()
{
//Code for get all user from DB
}
}
public interface IUserResolverService
{
string GetCustomerId();
}
public class UserResolverService : IUserResolverService
{
public string GetCustomerId()
{
//Code for Getting customerID.
}
}
在这里您可以使用 mock
进行测试
public class UserOperationTest
{
private readonly UserOperation _sut;
private readonly IUserResolverService _userResolverService;
private readonly IUsersRepository _userRepository;
public UserOperationTest()
{
_userResolverService = Substitute.For<IUserResolverService>();
_userRepository = Substitute.For<IUsersRepository>();
_sut = new UserOperation(_userResolverService, _userRepository);
}
[Fact]
public void CreateUser_SuccessWithMock()
{
// Arrange
var userDto = new UserDTO
{
Email = "ThirdUserUserEmail.Com"
};
var userList = new List<User>()
{
new User{CustomerId = "1", Email = "FirstUserEmail.Com"},
new User{CustomerId = "2", Email = "SecondUserEmail.Com"}
};
_userResolverService.GetCustomerId().Returns("3");
_userRepository.GetAllAsync().Returns(userList);
_userRepository.When(x => x.AddAsync(Arg.Any<User>())).Do(x =>
{
userList.Add(new User {Email = userDto.Email, CustomerId = userDto.CustomerId});
});
//Act
var result = _sut.CreateUser(userDto);
// Assert
result.Result.Should().BeTrue();
_userResolverService.Received(1).GetCustomerId();
_userRepository.Received(1).GetAllAsync();
_userRepository.Received(1).AddAsync(Arg.Any<User>());
userList.Count.Should().Be(3);
}
}
我是单元测试的新手,任何人都可以建议如何使用 xUnit 和 Moq 测试下面的 public 方法 (CreateUser),谢谢!
public async Task<bool> CreateUser(UserDTO newUser)
{
newUser.CustomerId = _userResolverService.GetCustomerId();
if (await CheckUserExists(newUser)) return false;
var salt = GenerateSalt(10);
var passwordHash = GenerateHash(newUser.Password, salt);
await _usersRepository.AddAsync(new User()
{
Role = newUser.Role,
CretedOn = DateTime.Now,
CustomerId = newUser.CustomerId,
Email = newUser.Email,
FirstName = newUser.FirstName,
LastName = newUser.LastName,
PasswordHash = passwordHash,
Salt = salt,
UpdatedOn = DateTime.Now
});
return true;
}
下面是私有方法 检查用户是否仅存在 returns 现有用户数
private async Task<bool> CheckUserExists(UserDTO user)
{
var users = await _usersRepository.GetAllAsync();
var userCount = users.Count(u => u.Email == user.Email);
return userCount > 0;
}
哈希生成
private static string GenerateHash(string input, string salt)
{
var bytes = System.Text.Encoding.UTF8.GetBytes(input + salt);
var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(bytes);
return ByteArrayToString(hash);
}
盐世代
private static string GenerateSalt(int size)
{
var rng = RandomNumberGenerator.Create();
var buff = new byte[size];
rng.GetBytes(buff);
return Convert.ToBase64String(buff);
}
private static string ByteArrayToString(byte[] ba)
{
var hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
谢谢, J
编辑 1
这是我目前所拥有的:
[Fact]
public async void CreateUser_True()
{
//arrange
var dataSource = new Mock<IRepository<User, int>>();
var userResolverService = new Mock<IUserResolverService>();
var tokenService = new Mock<ITokenService>();
var users = new List<User>();
users.Add(new User()
{
Email = "test@test.com",
CustomerId = 1
});
dataSource.Setup(m => m.GetAllAsync()).ReturnsAsync(users); // Error Here with converting async task to IEnumerable...
var accountService = new AccountService(dataSource.Object,userResolverService.Object,tokenService.Object);
//act
//assert
}
主要问题是我不知道如何模拟、设置私有方法 "CheckUserExists" 的行为,因为它正在调用 IRepository,它被模拟为 class,在这种情况下也是如此 return 我的 GetAllAsync 模拟设置来自这个私有方法?
编辑 2
public async Task<TEntity> AddAsync(TEntity entity)
{
if (entity == null)
{
throw new ArgumentNullException(nameof(entity));
}
_entities.Add(entity);
await _context.SaveChangesAsync();
return entity;
}
编辑 3
[Fact]
public async void CreateUser_True()
{
//arrange
var users = new List<User>();
users.Add(new User()
{
Email = "test@test.com",
CustomerId = 1
});
_dataSource.Setup(m => m.GetAllAsync()).ReturnsAsync(users);
_dataSource.Setup(m => m.AddAsync(It.IsAny<User>())).Returns<User>(Task.FromResult);
var accountService = new AccountService(_dataSource.Object, _userResolverService.Object, _tokenService.Object);
//act
var result = await accountService.CreateUser(new UserDTO()
{
Email = "truetest@test.com"
});
var updatedUsersList = await _dataSource.Object.GetAllAsync();
var usersCount = updatedUsersList.Count();
//assert
Assert.True(result);
Assert.Equal(2, usersCount);
}
由于被测试的方法是异步的,您需要设置所有异步依赖项以允许方法流完成。至于私有方法,您想设置该方法中使用的任何依赖项的行为,在本例中是用户存储库。
[Fact]
public async Task CreateUser_True() {
//arrange
var usersRepository = new Mock<IRepository<User, int>>();
var userResolverService = new Mock<IUserResolverService>();
var tokenService = new Mock<ITokenService>();
var user = new User() {
Email = "test@test.com",
CustomerId = 1
};
var users = new List<User>() { user };
usersRepository.Setup(_ => _.GetAllAsync()).ReturnsAsync(users);
usersRepository.Setup(_ => _.AddAsync(It.IsAny<User>()))
.Returns<User>(arg => Task.FromResult(arg)) //<-- returning the input value from task.
.Callback<User>(arg => users.Add(arg)); //<-- use call back to perform function
userResolverService.Setup(_ => _.GetCustomerId()).Returns(2);
var accountService = new AccountService(usersRepository.Object, userResolverService.Object, tokenService.Object);
//act
var actual = await accountService.CreateUser(new UserDto {
Email = "email@example.com",
Password = "monkey123",
//...other code removed for brevity
});
//assert
Assert.IsTrue(actual);
Assert.IsTrue(users.Count == 2);
Assert.IsTrue(users.Any(u => u.CustomerId == 2);
}
继续阅读 Moq Quickstart 以更好地了解如何使用模拟框架。
@sziszu 据我正确理解,您可以为 public 方法的单元测试设置模拟。这是解决方案。
我重写了你的代码,我的项目结构看起来像 visual studio project structure
这是我的代码片段
用户操作Class
public class UserOperation
{
#region Fields
private readonly IUserResolverService _userResolverService;
private readonly IUsersRepository _usersRepository;
#endregion
#region Constructor
public UserOperation(IUserResolverService userResolverService, IUsersRepository usersRepository)
{
_userResolverService = userResolverService;
_usersRepository = usersRepository;
}
#endregion
#region Public Methods
public async Task<bool> CreateUser(UserDTO newUser)
{
newUser.CustomerId = _userResolverService.GetCustomerId();
if (await CheckUserExists(newUser)) return false;
var salt = GenerateSalt(10);
var passwordHash = GenerateHash(newUser.Password, salt);
await _usersRepository.AddAsync(new User
{
Role = newUser.Role,
CretedOn = DateTime.Now,
CustomerId = newUser.CustomerId,
Email = newUser.Email,
FirstName = newUser.FirstName,
LastName = newUser.LastName,
PasswordHash = passwordHash,
Salt = salt,
UpdatedOn = DateTime.Now
});
return true;
}
#endregion
#region PrivateMethods
private async Task<bool> CheckUserExists(UserDTO user)
{
var users = await _usersRepository.GetAllAsync();
var userCount = users.Count(u => u.Email == user.Email);
return userCount > 0;
}
private static string GenerateHash(string input, string salt)
{
var bytes = Encoding.UTF8.GetBytes(input + salt);
var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(bytes);
return ByteArrayToString(hash);
}
private static string GenerateSalt(int size)
{
var rng = RandomNumberGenerator.Create();
var buff = new byte[size];
rng.GetBytes(buff);
return Convert.ToBase64String(buff);
}
private static string ByteArrayToString(byte[] ba)
{
var hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
#endregion
}
以下是我的存储库和服务。
public interface IUsersRepository
{
Task AddAsync(User user);
Task<ICollection<User>> GetAllAsync();
}
public class UsersRepository : IUsersRepository
{
public Task AddAsync(User user)
{
//Code for adding user
}
public Task<ICollection<User>> GetAllAsync()
{
//Code for get all user from DB
}
}
public interface IUserResolverService
{
string GetCustomerId();
}
public class UserResolverService : IUserResolverService
{
public string GetCustomerId()
{
//Code for Getting customerID.
}
}
在这里您可以使用 mock
进行测试 public class UserOperationTest
{
private readonly UserOperation _sut;
private readonly IUserResolverService _userResolverService;
private readonly IUsersRepository _userRepository;
public UserOperationTest()
{
_userResolverService = Substitute.For<IUserResolverService>();
_userRepository = Substitute.For<IUsersRepository>();
_sut = new UserOperation(_userResolverService, _userRepository);
}
[Fact]
public void CreateUser_SuccessWithMock()
{
// Arrange
var userDto = new UserDTO
{
Email = "ThirdUserUserEmail.Com"
};
var userList = new List<User>()
{
new User{CustomerId = "1", Email = "FirstUserEmail.Com"},
new User{CustomerId = "2", Email = "SecondUserEmail.Com"}
};
_userResolverService.GetCustomerId().Returns("3");
_userRepository.GetAllAsync().Returns(userList);
_userRepository.When(x => x.AddAsync(Arg.Any<User>())).Do(x =>
{
userList.Add(new User {Email = userDto.Email, CustomerId = userDto.CustomerId});
});
//Act
var result = _sut.CreateUser(userDto);
// Assert
result.Result.Should().BeTrue();
_userResolverService.Received(1).GetCustomerId();
_userRepository.Received(1).GetAllAsync();
_userRepository.Received(1).AddAsync(Arg.Any<User>());
userList.Count.Should().Be(3);
}
}