I am failing on the BeUniqueEmail with the error message (below). How can I mock this correctly? I have included the validator and the test below.
The tests passed when I removed the BeUnique email validation
Message:
System.NotImplementedException : The method or operation is not implemented.
public class CreateStudentCommandValidator : AbstractValidator<CreateStudentCommand>
{
private readonly IApplicationDbContext _context;
public CreateStudentCommandValidator(IApplicationDbContext context)
{
_context = context;
RuleFor(v => v.Email)
.NotEmpty().WithMessage("Email is required.")
.MaximumLength(30).WithMessage("Email must not exceed 30 characters.")
.MustAsync(BeUniqueEmail).WithMessage("The specified email already exists.");
}
public async Task<bool> BeUniqueEmail(CreateStudentCommand model, string email, CancellationToken cancellationToken)
{
bool emailExists = await _context.Students
.Where(x => x.Email == email)
.Where(x => !x.IsDeleted)
.CountAsync() > 0;
return !emailExists;
}
}
Testing
[Test]
public async Task CreateStudentCommand_Success()
{
var mockSet = new Mock<DbSet<Student>>();
var context = new Mock<IApplicationDbContext>();
context.Setup(m => m.Student).Returns(mockSet.Object);
var handler = new CreateStudentCommandHandler(context.Object);
var validator = new CreateStudentCommandValidator(context.Object);
//var mockedValidator = new Mock<IValidator<CreateStudentCommandValidator>>();
//var mock1 = new Mock<AbstractValidator<CreateStudentCommandValidator>>();
var command = new CreateStudentCommand
{
StudentType = "Test1",
Email = "Test1#email.com",
FirstName = "Test1",
LastName = "Test1",
IsActive = true
};
var result = await handler.Handle(command, new CancellationToken());
// Act
var validationResult = await validator.ValidateAsync(command);
// Assert
Assert.True(validationResult.IsValid);
Assert.IsInstanceOf<Guid>(result);
}
Using Moq, a solution to this could be as follows.
[Test]
public async Task TestValidation()
{
var context = Mock.Of<ApplicationDbContext>();
var validator = new CreateStudentCommandValidator(context);
var command = new CreateStudentCommand
{
StudentType = "Test1",
Email = "Test1#email.com",
FirstName = "Test1",
LastName = "Test1",
IsActive = true
};
var validationResult = await validator.ValidateAsync(command);
Assert.True(validationResult.IsValid);
}
Not knowing your Context, I implemented a simple dummy.
public interface IApplicationDbContext
{
List<Student> Students { get; set; }
}
public class ApplicationDbContext : IApplicationDbContext
{
public ApplicationDbContext()
{
Students = new List<Student>();
}
public List<Student> Students { get; set; }
}
This all depends on the use of Moq however, If you are using some other Mocking Service the Implementation will change.
*My Previous answer was intended to explain the implementation of Mocking Method Results. But in this case you do not need to mock the CreateStudentCommandValidator
Related
So I've trying to test my webapp coded in .net core 5.0, and I have a classic MVC model with service classes. Now I am trying to unit test the solution with moq to mock my database, and my tests run, but I have just noticed that they are all wrong when I debug. It seems as if it doesn't actually connect to the mock service or database itself... I have gone for mocking the service interface but that doesn't seem to work still. Help would be greatly appreciated.
Service class:
public class InventoryService : IInventoryService
{
private readonly DBContext _db;
public InventoryService(DBContext db)
{
_db = db;
}
public List<Inventory> GetInventories(string id)
{
var inventories = (from i in _db.Inventories where i.userId.Equals(id) select i).ToList();
return inventories;
}
public void CreateInventory(Inventory newInventory)
{
_db.Inventories.Add(newInventory);
_db.SaveChanges();
}
public bool DeleteInventory(Guid Id, string UserId)
{
var inventory = _db.Inventories.Find(Id);
if (inventory == null)
return false;
if (inventory.userId != UserId)
return false;
//Delete items using item service
var items = from i in _db.Items where i.inventoryId.Equals(Id) select i;
foreach(var i in items)
{
_db.Items.Remove(i);
}
_db.Inventories.Remove(inventory);
_db.SaveChanges();
return true;
}
}
}
Service interface it uses:
public interface IInventoryService
{
public List<Inventory> GetInventories(string id);
public void CreateInventory(Inventory newInventory);
public bool DeleteInventory(Guid Id, string UserId);
}
}
Model:
public class Inventory
{
// The Id field is a unique identifier for a specific inventory
[Key]
public Guid Id { get; set; }
// The name of the Inventory. Required to have a value
[Required]
[DisplayName("Inventory Name")]
public string name { get; set; }
public string userId { get; set; }
}
}
Unit test:
public class InventoryTests
{
// Unit test defined for the get user inventories, a valid ID will always be passed in so no need for negative testing
[Fact]
public void Get_user_inventories_with_valid_id()
{
//ARRANGE
Guid theId1 = new("00000000-0000-0000-0000-000000000001");
Guid theId2 = new("00000000-0000-0000-0000-000000000002");
string u1 = "xxx";
string u2 = "yyy";
var mockIn = new Mock<IInventoryService>();
var data = new List<Inventory>
{
new Inventory { Id = theId1, name = "Mums 1", userId = u1},
new Inventory { Id = theId1, name = "Mums 1.2", userId = u1},
}.AsQueryable();
var mockSet = new Mock<DbSet<Inventory>>();
mockSet.As<IQueryable<Inventory>>().Setup(m => m.Provider).Returns(data.Provider);
mockSet.As<IQueryable<Inventory>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<Inventory>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<Inventory>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
Mock<IInventoryService> myser = new Mock<IInventoryService>();
//ACT
myser.Setup(x => x.GetInventories(u1)).Returns(mockSet.Object.ToList());
var tinvs = myser.Object.GetInventories(u1);
//ASSERT
Assert.Equal(2 , tinvs.Count);
}
//Unit test for creating an inventory, a valid new inventory object will always be passed in so need for negative testing
[Fact]
public void Creating_An_Inventory()
{
//ARRANGE
Guid theId2 = new("00000000-0000-0000-0000-000000000001");
string u1 = "xxx";
string u2 = "yyy";
var mockIn = new Mock<IInventoryService>();
var data = new List<Inventory>
{
}.AsQueryable();
var mockSet = new Mock<DbSet<Inventory>>();
mockSet.As<IQueryable<Inventory>>().Setup(m => m.Provider).Returns(data.Provider);
mockSet.As<IQueryable<Inventory>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<Inventory>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<Inventory>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
//ACT
Mock<IInventoryService> myser = new Mock<IInventoryService>();
myser.Setup(x => x.GetInventories(u1)).Returns(mockSet.Object.ToList());
Inventory thenew = new Inventory { Id = theId2, name = "Mums 2", userId = u2 };
myser.Object.CreateInventory(thenew);
//ASSERT
Assert.NotNull(data);
}
//Unit test for deleting an inventory. It will always be valid because a guid and user id will be passed in automatically
[Fact]
public void Deleting_An_Inventory()
{
//ARRANGE
Guid theId1 = new("00000000-0000-0000-0000-000000000001");
Guid theId2 = new("00000000-0000-0000-0000-000000000002");
string u1 = "123";
//var mockIn = new Mock<IInventoryService>();
var data = new List<Inventory>
{
new Inventory { Id = theId1, name = "testinv 1", userId = u1},
}.AsQueryable();
var mockSet = new Mock<DbSet<Inventory>>();
mockSet.As<IQueryable<Inventory>>().Setup(m => m.Provider).Returns(data.Provider);
mockSet.As<IQueryable<Inventory>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<Inventory>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<Inventory>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
//ACT
Mock<IInventoryService> myser = new Mock<IInventoryService>();
myser.Setup(x => x.GetInventories(u1)).Returns(mockSet.Object.ToList());
myser.Object.DeleteInventory(theId1, u1);
var updInvs = myser.Object.GetInventories(u1);
int x = updInvs.Count;
//ASSERT
Assert.Equal(0, x);
}
}
}
I am fairly sure it is a minor error in my setup of the unit test's mocking. any help would be great.
So, if you want to test your InventoryService, you have to mock your dbContext as you do. Better use EF InMemoryDatabase like Microsoft advises https://learn.microsoft.com/en-us/ef/core/testing/#unit-testing
About your tests, I make a few modifications, to test InventoryService. It passes in debug mode.
public class InventoryService : IInventoryService
{
private readonly DBContext _db;
public InventoryService(DBContext db)
{
_db = db;
}
public List<Inventory> GetInventories(string id)
{
//i change inventories to _db.Set<Inventory> which able to be mock
var inventories = (from i in _db.Set<Inventory>() where i.userId.Equals(id) select i).ToList();
return inventories;
}
//ANOTHER CODE
}
[Fact]
public void Get_user_inventories_with_valid_id()
{
//ARRANGE
Guid theId1 = new("00000000-0000-0000-0000-000000000001");
Guid theId2 = new("00000000-0000-0000-0000-000000000002");
string u1 = "xxx";
string u2 = "yyy";
var mockIn = new Mock<IInventoryService>();
var data = new List<Inventory>
{
new Inventory { Id = theId1, name = "Mums 1", userId = u1 },
new Inventory { Id = theId1, name = "Mums 1.2", userId = u1 },
}.AsQueryable();
var mockSet = new Mock<DbSet<Inventory>>();
mockSet.As<IQueryable<Inventory>>().Setup(m => m.Provider).Returns(data.Provider);
mockSet.As<IQueryable<Inventory>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<Inventory>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<Inventory>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
//HERE
var dbMock = new Mock<DBContext>();
dbMock.Setup(x => x.Set<Inventory>()).Returns(mockSet.Object);
var myser = new InventoryService(dbMock.Object);
//ACT
//AND HERE
var tinvs = myser.GetInventories(u1);
//ASSERT
Assert.Equal(2, tinvs.Count);
}
If you want to make InMemory db or test on real SQL db, you look at these questions.
Unit testing EF Core using in-memory database with an eager-loaded function
Unit testing with EF Core and in memory database
I have a service class like this:
public class CategoryService: ICategoryService
{
private myContext _context;
public CategoryService(myContext context)
{
_context = context;
}
public async Task<List<CategoryDTO>> GetCategories()
{
return (await _context.Categories.ToListAsync()).Select(c => new CategoryDTO
{
CategoryId = c.CategoryId,
CategoryName = c.CategoryName
}).ToList();
}
}
My context looks like this:
public DbSet<Category> Categories {get;set;}
My unit test for GetCategories() is:
[Fact]
public void TestGetCategories()
{
//Arrange
Mock <myContext> moq = new Mock <myContext>();
var moqSet = new Mock<DbSet<Category>>();
moq.Setup(m => m.Categories).Returns(moqSet.Object);
CategoryService service = new CategoryService(moq.Object);
//Act
var result = service.GetCategories();
//Assert
Assert.NotNull(result);
}
But I am getting error for my unit test. It says:
System.NotSupportedException : Unsupported expression: m => m.Categories
Can someone help me to fix the Setup part?
I finally could figure it out.
As #PeterCsala mentioned, we can use "EntityFrameworkCore3Mock"
You can find it here: https://github.com/huysentruitw/entity-framework-core3-mock
My unit test looks like this:
public DbContextOptions<ShoppingCartContext> dummyOptions { get; } = new DbContextOptionsBuilder<ShoppingCartContext>().Options;
[Fact]
public async Task TestGetCategories()
{
//Arrange
var dbContextMoq = new DbContextMock<ShoppingCartContext>(dummyOptions);
//Create list of Categories
dbContextMoq.CreateDbSetMock(x => x.Categories, new[]
{
new Category { CategoryId = 1, CategoryName = "Items" },
new Category { CategoryId = 2, CategoryName = "Fruits" }
});
//Act
CategoryService service = new CategoryService(dbContextMoq.Object);
var result = await service.GetCategories();
//Assert
Assert.NotNull(result);
}
You cannot use Moq with non overrideable properties. It needs to be either abstract or virtual and that's why you get the error.
Change the dbcontext property Categories to virtual and try again.
public virtual DbSet<Category> Categories {get;set;}
P.s. you don't need to do this when you mock interface methods, because they are inherently overridable.
I try to create the tests for my API controller method. In a simple way, I write the add method.
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> AddAsync([FromBody]BaseUserDTO dto)
{
if (ModelState.IsValid)
{
return Ok();
}
else
{
return ValidationProblem();
}
}
in dto model I have
public class BaseUserDTO
{
[Required(ErrorMessage = "This field should be filled in", AllowEmptyStrings = false)]
[RegularExpression(#"(^[a-zA-Z0-9_ -]+$)", ErrorMessage = ("Use only Latin characters"))]
public string FirstName { get; set; }
[Required(ErrorMessage = "This field should be filled in", AllowEmptyStrings = false)]
[RegularExpression(#"(^[a-zA-Z0-9_ -]+$)", ErrorMessage = ("Use only Latin characters"))]
public string LastName { get; set; }
}
I wrote the first try to test the data annotation
[Fact]
public async Task UserValidationError()
{
//Arrange
BaseUserDTO userDTO = new BaseUserDTO
{
FirstName = "222A###",
LastName = "Test",
Email = "Test#test.com",
PhoneNumber = "(111)111-1111",
Role = 0,
Password = "1234567A",
RetypePassword = "1234567A"
};
UserController controller = new UserController(userServicesMock.Object, mapperMock.Object, loggerMock.Object);
//Act
IActionResult result = await controller.AddAsync(userDTO);
//Assert
Assert.IsType<BadRequestObjectResult>(result);
}
but the model state always true, and I don't understand why it happened????
My second try take NullReference in mongo connection when startup started (in a normal situation it works good(when the application works), check it by postman)
[Fact]
public async Task UserValidationError(string userState)
{
//Arrange
BaseUserDTO userDTO = new BaseUserDTO
{
FirstName = "222A###",
LastName = "Test",
Email = "Test#test.com",
PhoneNumber = "(111)111-1111",
Role = 0,
Password = "1234567A",
RetypePassword = "1234567A"
};
var b = new WebHostBuilder().UseStartup<Startup>().UseEnvironment("development");
var server = new TestServer(b) { BaseAddress = new Uri(#"http://localhost:54133") };
var client = server.CreateClient();
var json = JsonConvert.SerializeObject(userDTO);
var content = new StringContent(json, Encoding.UTF8, "application/json");
UserController controller = new UserController(userServicesMock.Object, mapperMock.Object, loggerMock.Object);
//Act
var result = await client.PostAsync("api/User", content);
//assert
Assert.Equal(400, (int)result.StatusCode);
}
How normally create the unit test to check the DataAnnotation validation? Or how right check this validation?
using System.ComponentModel.DataAnnotations;
/*other using*/
[Fact]
public void UserValidationError()
{
//Arrange
BaseUserDTO userDTO = new BaseUserDTO
{
FirstName = "222A###",
LastName = "Test",
Email = "Test#test.com",
PhoneNumber = "(111)111-1111",
Role = 0,
Password = "1234567A",
RetypePassword = "1234567A"
};
//ACT
var lstErrors = ValidateModel(userDTO);
//ASSERT
Assert.IsTrue(lstErrors.Count == 1);
//Or
Assert.IsTrue(lstErrors.Where(x => x.ErrorMessage.Contains("Use only Latin characters")).Count() > 0);
}
//http://stackoverflow.com/questions/2167811/unit-testing-asp-net-dataannotations-validation
private IList<ValidationResult> ValidateModel(object model)
{
var validationResults = new List<ValidationResult>();
var ctx = new ValidationContext(model, null, null);
Validator.TryValidateObject(model, ctx, validationResults, true);
return validationResults;
}
More details in Microsoft Site : https://learn.microsoft.com/en-us/aspnet/web-api/overview/testing-and-debugging/unit-testing-controllers-in-web-api
I have been teaching myself how to unit test various parts of my application. I am trying to do a unit test of my UserService. I seem to be getting the following error:
I am not sure what the error means. Could someone explain what is going on? I know the service itself works already I tested it separately. I have never created unit tests before so if someone could please give me a detailed explanation as to why this is happening?
Thanks in advance!
User Service Test
public class UserServiceTest
{
public readonly IUserService MockUserService;
public List<UserDTO> userDTOList = new List<UserDTO>
{
new UserDTO(new User{UserId = 1, FirstName = "Eric"}),
new UserDTO(new User{UserId = 1, FirstName = "Dave"}),
new UserDTO(new User{UserId = 1, FirstName = "Jim"})
};
public UserServiceTest()
{
Mock<IUserService> mockUserService = new Mock<IUserService>();
mockUserService.Setup(mr => mr.GetAllUsers()).Returns(userDTOList);
this.MockUserService = mockUserService.Object;
}
[TestMethod]
public void TestServiceGetUsers()
{
List<UserDTO> testUserList = this.MockUserService.GetAllUsers();
Assert.IsNotNull(testUserList);
Assert.AreEqual(userDTOList, testUserList);
}
}
WCF Interface
[ServiceContract]
public interface IUserService
{
[OperationContract]
List<UserDTO> GetAllUsers();
}
[DataContract]
public class UserDTO
{
public UserDTO(User user)
{
this.FirstName = user.FirstName;
this.LastName = user.LastName;
this.NumberOfItemsSold = user.NumberOfItemsSold;
this.UserId = user.UserId;
this.UserName = user.UserName;
}
[DataMember]
public int UserId { get; set; }
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string LastName { get; set; }
[DataMember]
public string UserName { get; set; }
[DataMember]
public int NumberOfItemsSold { get; set; }
}
WCF Interface implementation
public class UserService : IUserService
{
public List<UserDTO> GetAllUsers()
{
using (UnitOfWork work = new UnitOfWork(new ProductContext()))
{
return work.Users.GetAll().Select(a => new UserDTO(a)).ToList();
}
}
}
Edit after suggestion from Mel Gerats
Okay so I figured I would place the updated code for my unit test that actually tests the service now. Here it is! Thanks again for the help.
[TestClass]
public class UserServiceTest
{
public List<UserDTO> userDTOList = new List<UserDTO>
{
new UserDTO(new User{UserId = 1, FirstName = "Eric"}),
new UserDTO(new User{UserId = 2, FirstName = "Dave"}),
new UserDTO(new User{UserId = 3, FirstName = "Jim"})
};
public IEnumerable<User> users = new List<User>
{
new User{UserId = 1, FirstName = "Eric"},
new User{UserId = 2, FirstName = "Dave"},
new User{UserId = 3, FirstName = "Jim"}
};
[TestMethod]
public void TestServiceGetUsers()
{
//Setup
var unitOfWorkMock = new Mock<IUnitOfWork>();
unitOfWorkMock.Setup(m => m.Users.GetAll()).Returns(users);
var serviceUnderTest = new UserService(unitOfWorkMock.Object);
var result = serviceUnderTest.GetAllUsers();
Assert.IsNotNull(result);
Assert.AreEqual(userDTOList.Count, result.Count);
Assert.AreEqual(userDTOList[0].FirstName, result[0].FirstName);
Assert.AreEqual(userDTOList[1].FirstName, result[1].FirstName);
}
}
For some reason I could not directly test if the two lists were equal. I think it is because the Capacity of the lists were different on my lists. So I opted for testing each position in the list.
Looks like you are missing a reference to System.ServiceModel
But your test doesn't actually test anything, except maybe that mocking works, as you are only calling the mocked method on the mock.
The goal of mocking is to test your class under test while mocking the dependencies. If your UserService had a dependency on a UserRepository for example, you would mock the repository in order to test the UserService.
The following example illustrates this:
public class UserService : IUserService
{
private IUserRepository _userRepository;
private IUserNotification _userNotification;
public UserService(IUserRepository userRepository, IUserNotification userNotification)
{
_userRepository = userRepository;
_userNotification = userNotification;
}
public void NotifyInactiveUsers(DateTime activeBefore)
{
var users = _userRepository.GetAllUsers();
foreach(var user in users)
{
if(user.LastActivityDate > DateTime.Now)
{
_userNotification.SendRemovalNotification(user)
_userRepository.MarkUserForRemoval(user)
}
}
}
}
The UserService provides an operation to notify all users that are inactive since a given date.
public void TestUserService_NotifyInactiveUsers_BeforeActivityDate()
{
var userRepository = new Mock<IUserRepository>();
var userNotification = new Mock<IUserNotification>();
var userService = new UserService(userRepository.Object, userNotification.Object);
var testUser = new User { LastActivityDate = DateTime.Now}
userRepository.Setup(r => r.GetAllUsers()).Returns(Enumerable.Repeat(testUser, 1));
userService.NotifyInactiveUsers(DateTime.Now.AddYears(-1));
userNotification.Verify(n => n.SendRemovalNotification(It.IsAny<User>), Times.Never);
}
The test sets up the UserRepository to return one user that was active today.
It then calls the operation on the UserService, and verifies that SendRemovalNotification was never called. This tests whether or not the logic to determine if a user is inactive is indeed correct.
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 = "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
}
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 = "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);
}
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 = "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);
}
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);
}
}