devlooped / moq

The most popular and friendly mocking framework for .NET
Other
5.95k stars 801 forks source link

C# Xunit & Moq mocking generic repository, unitOfWork repository always returns null #1323

Open deva7mad opened 2 years ago

deva7mad commented 2 years ago

The problem is when I try to mock any method of the repository, for example GetFirstOne, it always return null during debugging.

C# mocking generic repository, unitOfWork and applying filter expression return exception that because repository always returns null

Method in service class that I wrote test for it:

    public bool ResetPassword(ChangePasswordDto changePasswordDto)
    {
        var userQueryCriteria = UserOfMobileQueryCriteria(changePasswordDto.MobileNumber);

        var user = _unitOfWork.UserRepository.GetFirstOne(userQueryCriteria);

        if (user is null)
            throw new RecordNotFoundException(_configReader.GetKey(ConfigKeys.ExceptionRecordNotFound.ToString()),
                SharedErrorCode.RecordNotFound);

        var verification = _verificationService.IsRecentlyVerified(user.Id, changePasswordDto.MobileNumber, changePasswordDto.NotificationChannelType);

        if (verification is {IsVerified: false})
            throw new UserNotVerifiedException(_configReader.GetKey(ConfigKeys.ExceptionUserIsNotVerified.ToString()),
                SharedErrorCode.UserNotVerified);

        var newPassword = _generalUtility.HashPassword(changePasswordDto.Password,
            _configReader.GetKey(ConfigKeys.HashSalt.ToString()));

        if (user.Password == newPassword)
            throw new AuthenticationException(
                _configReader.GetKey(ConfigKeys.ExceptionNewPasswordIsSimilarOldOne.ToString()),
                SharedErrorCode.NewPasswordLikeOldOne);

        user.Password = newPassword;
        _unitOfWork.UserRepository.Update(user);
        _unitOfWork.Save();

        return true;
    }

Implementation of UserOfMobileQueryCriteria method:

protected virtual BaseQueryCriteria<UserEntity> UserOfMobileQueryCriteria(string mobileNumber)
{
    return new BaseQueryCriteria<UserEntity>(_ => _.MobileNumber == mobileNumber);
}

Implementation of GetFirstOne method:

public override T? GetFirstOne(IQuerySpecification<T> specification)
{
  return ApplySpecification(specification).FirstOrDefault();
}

private IQueryable<T> ApplySpecification(IQuerySpecification<T> spec)
{
    return GetQuery(_dbContext.Set<T>().AsQueryable(), spec);
}

private static IQueryable<T> GetQuery(IQueryable<T> queryable, IQuerySpecification<T> specification)
{
   var query = queryable;
   query = query.Where(specification.Filter);

   return query;
}

Test:

[Fact]
public void ResetPasswordReturnTrue()
{
 //Arrange
  var changePasswordDto = new ChangePasswordDto()
  {
    Code = "1234",
    Password = "stringst",
    PasswordConfirmation = "stringst",
    MobileNumber = "+111111111111",
    NotificationChannelType = NotificationChannelType.SMS
   };

   var user = new UserEntity
   {
     Id = Guid.NewGuid().ToString(),
     MobileNumber = changePasswordDto.MobileNumber,
     Role = UserRoles.Parent,
            FirstName = "Ahmad",
            LastName = "Ke"
        };

        Expression<Func<UserEntity, bool>> e = x => x.MobileNumber == changePasswordDto.MobileNumber;

        var userQueryBuilderMock = new Mock<BaseQueryCriteria<UserEntity>>(e);

        _userServiceMock.Protected()
            .Setup<BaseQueryCriteria<UserEntity>>("UserOfMobileQueryCriteria", changePasswordDto.MobileNumber)
            .Returns(  userQueryBuilderMock.Object );

        _userUnitOfWorkMock.Setup(r =>
                r.UserRepository.GetFirstOne(userQueryBuilderMock.Object))
            .Returns(user);

        var verification = new VerificationResponseDto
        {
            Code = changePasswordDto.Code,
            Id = Guid.NewGuid().ToString(),
            Recipient = user.MobileNumber,
            IsVerified = true,
            SendDate = DateTime.Today.AddMinutes(-10),
            UserId = user.Id,
            NotificationChannelType = changePasswordDto.NotificationChannelType
        };

  _verificationServiceMock.Setup(v => v.IsRecentlyVerified(user.Id, user.MobileNumber,NotificationChannelType.SMS)).Returns(verification);

  //Act
  var passwordChanged = _passwordService.ResetPassword(changePasswordDto);

  //Assert
  Assert.True(passwordChanged);
}

IUserUnitOfWork & UserUnitOfWork

public interface IUserUnitOfWork : IUnitOfWork
{
   IRepository<UserEntity> UserRepository { get; }
}

public class UserUnitOfWork : IUserUnitOfWork
{
  private readonly UserDbContext _userContext;
  public IRepository<UserEntity> UserRepository { get; }

  public UserUnitOfWork(UserDbContext userContext, IRepository<UserEntity> userRepository)
 {
  _userContext = userContext;

 UserRepository = userRepository;
 }

        public void Save()
        {
            _userContext.SaveChanges();
        }

        public void Dispose()
        {
            _userContext.Dispose();
        }
    }

Result exception:

Exceptions.RecordNotFoundException

Exception of type 'Exceptions.RecordNotFoundException' was thrown.

at Application.UserService.ResetPassword(ChangePasswordDto changePasswordDto) in Application\UserPasswordService.cs:line 23
at UnitTests.User.Application.UserServiceTest.ResetPasswordReturnTrue() in User\Application\UserServiceTest.cs:line 91

Back this issue Back this issue

stakx commented 1 year ago

Is this simply a usage question or do you believe this is a bug in Moq?

That repro code is rather non-minimal. Is there any chance you could trim that down any further, @deva7mad? If not, it would help if you could provide this as a VS project so we don't need to piece this together ourselves.