For creating fake data I’ve been using Bogus package together with Theory class for my unit tests. But as expected I have some classes I need fake data of them such as entities and dtos.
Here is my entity user
public sealed class User
{
public Guid Id { get; set; }
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string Username { get; set; } = string.Empty;
public bool IsActive { get; set; }
public int RoleId { get; set; }
public Role Role { get; set; } = null!;
}
Here are my dtos.
public sealed record CreateUserRequest(
[Required, StringLength(maximumLength:50)]
string FirstName,
[Required, StringLength(maximumLength:70)]
string LastName,
[Required, StringLength(maximumLength:25)]
string UserName,
[Required, StringLength(maximumLength:70), DataType(DataType.EmailAddress), EmailAddress]
string Email,
[Required, MinLength(5), DataType(DataType.Password)]
string Password,
[Required, Range(1, int.MaxValue)]
int RoleId
);
public sealed record UserResponse(
Guid Id,
string Name,
string LastName,
string Email
);
So, I created this static class:
internal static class UserTestDataFactory
{
public static List<User> CreateManyUsers(int count)
{
Faker<User> userFaker = new Faker<User>()
.RuleFor(u => u.FirstName, f => f.Name.FirstName())
.RuleFor(u => u.LastName, f => f.Name.LastName())
.RuleFor(u => u.Email, f => f.Internet.Email())
.RuleFor(u => u.Password, f => f.Internet.Password())
.RuleFor(u => u.Username, f => f.Internet.UserName())
.RuleFor(u => u.IsActive, f => f.Random.Bool());
return userFaker.Generate(count);
}
public static List<CreateUserRequest> CreateManyUserRequests(int count)
{
Faker<CreateUserRequest> faker = new Faker<CreateUserRequest>()
.CustomInstantiator(f =>
new CreateUserRequest(
f.Name.FirstName(),
f.Name.LastName(),
f.Internet.UserName(),
f.Internet.Email(),
f.Internet.Password(),
f.Random.Number(1, 7)));
return faker.Generate(count);
}
public static User CreateSingleUser()
{
return CreateManyUsers(1).First();
}
public static CreateUserRequest CreateSingleUserRequest()
{
return CreateManyUserRequests(1).First();
}
public static List<UserResponse> CreateManyUserResponses(int count)
{
var fakerUserResponse = new Faker<UserResponse>()
.CustomInstantiator(f =>
new UserResponse(
Guid.NewGuid(),
f.Name.FirstName(),
f.Internet.Email(),
f.Name.LastName()));
return fakerUserResponse.Generate(count, nameof(fakerUserResponse));
}
}
So far, I don’t see anything wrong with it. But what if I end up having many more classes? Should I continue using the same approach or change to another?
Here are my test data for my unit test.
public class CreateUserValidTestData : TheoryData<CreateUserRequest>
{
public CreateUserValidTestData()
{
Add(UserTestDataFactory.CreateSingleUserRequest());
Add(UserTestDataFactory.CreateSingleUserRequest());
Add(UserTestDataFactory.CreateSingleUserRequest());
}
}
public class UserListValidTestData : TheoryData<List<User>>
{
public UserListValidTestData()
{
/*var testData = new TestDataFactory<UserData, User>().Create();
Add(testData.Multiple(6));
Add(testData.Multiple(1));
Add(testData.Multiple(4));
Add(testData.Multiple(10));*/
Add(UserTestDataFactory.CreateManyUsers(2));
Add(UserTestDataFactory.CreateManyUsers(5));
Add(UserTestDataFactory.CreateManyUsers(6));
}
}
Here I am using UserListValidTestData
and CreateUserValidTestData
classes
public class UserServiceTest
{
[Theory, ClassData(typeof(UserListValidTestData))]
public async Task GetAllAsync_Should_ReturnUserList_WhenUsersExist(IReadOnlyList<User> usersExpected)
{
// Arrange
Mock<IUserRepository> mockUserRepository = new();
mockUserRepository
.Setup(repository => repository.GetAllAsync())
.ReturnsAsync(usersExpected)
.Verifiable(Times.Once());
UserService userService = new(mockUserRepository.Object, default!, default!);
// Act
var result = await userService.GetAllAsync();
// Assert
mockUserRepository.Verify();
result.IsSuccess
.Should()
.BeTrue();
result.Value
.Should()
.BeAssignableTo<IReadOnlyList<UserResponse>>();
result.Value
.Should()
.NotBeEmpty()
.And
.HaveSameCount(usersExpected);
}
[Theory, ClassData(typeof(CreateUserValidTestData))]
public async Task CreateAsync_Should_ReturnId_WhenUserDoesNotExist(
CreateUserRequest request)
{
// Arrange
User expected = new()
{
Id = Guid.NewGuid(),
FirstName = request.FirstName,
LastName = request.LastName,
Username = request.UserName,
Email = request.Email,
RoleId = request.RoleId,
IsActive = true
};
Mock<IUserRepository> mockUserRepository = new();
Mock<IUnitOfWork> mockUnitOfWork = new();
Mock<IPasswordHasher<User>> mockPasswordHasher = new();
mockPasswordHasher.Setup(provider => provider.HashPassword(It.IsAny<User>(), request.Password))
.Returns(It.IsAny<string>())
.Verifiable(Times.Once());
mockUserRepository
.Setup(repo => repo.ExistAsync(It.IsAny<string>()))
.ReturnsAsync(false)
.Verifiable(Times.Once());
mockUserRepository
.Setup(repo => repo.CreateAsync(It.IsAny<User>()))
.ReturnsAsync(expected)
.Verifiable(Times.Once());
mockUnitOfWork
.Setup(unitOfWork => unitOfWork.SaveChangesAsync(default))
.ReturnsAsync(1) // Simulate a successfull save operation affecting 1 row
.Verifiable(Times.Once());
UserService userService = new(
mockUserRepository.Object,
mockUnitOfWork.Object,
mockPasswordHasher.Object);
// Act
var result = await userService.CreateAsync(request);
// Assert
mockUserRepository.Verify();
mockUnitOfWork.Verify();
mockPasswordHasher.Verify();
result.IsSuccess
.Should()
.BeTrue();
result.Error
.Should()
.Be(Error.None);
result.Value
.Should()
.NotBeEmpty()
.And
.Be(expected.Id);
}
Pd: I’ve recently started with unit testing.
Thoughts or advice is gratefully received…
Skeytor is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.