I'm new to unit testing, can anyone advise how to test public method (CreateUser) below using xUnit and Moq, thanks!
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;
}
Private methods below Check if user exists simply returns number of existing users
private async Task<bool> CheckUserExists(UserDTO user)
{
var users = await _usersRepository.GetAllAsync();
var userCount = users.Count(u => u.Email == user.Email);
return userCount > 0;
}
Hash Generation
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);
}
Salt Generaion
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();
}
Thanks, J
EDIT 1
This is what I have so far:
[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 = "[email protected]",
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
}
Main problem is that I have no idea how to Mock, set up behavior of private Method "CheckUserExists", as it's calling IRepository which is mocked for class, so will this in this case return my mock setup for GetAllAsync from this private method?
EDIT 2
public async Task<TEntity> AddAsync(TEntity entity)
{
if (entity == null)
{
throw new ArgumentNullException(nameof(entity));
}
_entities.Add(entity);
await _context.SaveChangesAsync();
return entity;
}
EDIT 3
[Fact]
public async void CreateUser_True()
{
//arrange
var users = new List<User>();
users.Add(new User()
{
Email = "[email protected]",
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 = "[email protected]"
});
var updatedUsersList = await _dataSource.Object.GetAllAsync();
var usersCount = updatedUsersList.Count();
//assert
Assert.True(result);
Assert.Equal(2, usersCount);
}
Moq and xUnit belong to "Testing Frameworks" category of the tech stack. xUnit is an open source tool with 2.62K GitHub stars and 610 GitHub forks. Here's a link to xUnit's open source repository on GitHub.
You can use Moq to create mock objects that simulate or mimic a real object. Moq can be used to mock both classes and interfaces. However, there are a few limitations you should be aware of. The classes to be mocked can't be static or sealed, and the method being mocked should be marked as virtual.
A Mock Object is a powerful way to implement Behavior Verification (page X) while avoiding Test Code Duplication (page X) between similar tests by delegating the job of verifying the indirect outputs of the SUT entirely to a Test Double (page X).
Unit testing is a powerful way to ensure that your code works as intended. It's a great way to combat the common “works on my machine” problem. Using Moq, you can mock out dependencies and make sure that you are testing the code in isolation. Moq is a mock object framework for .
As the method being tested is async you need to setup all async dependencies to allow the method flow to completion. As for the private method, you want to setup the behavior of any dependencies that are used within that method, which in this case is the users repository.
[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 = "[email protected]",
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 protected]",
Password = "monkey123",
//...other code removed for brevity
});
//assert
Assert.IsTrue(actual);
Assert.IsTrue(users.Count == 2);
Assert.IsTrue(users.Any(u => u.CustomerId == 2);
}
Read up on Moq Quickstart to get a better understanding of how to use the mocking framework.
@sziszu As I understand correctly you can setup mock for unit test of your your public method. here is solution.
I have rewrite your code and my project structure is something look like visual studio project structure
Here is my code snippets
UserOperation 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
}
Below are my repositories and services.
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.
}
}
Here You can test using 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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With