moq/labs

Xunit: Moq Setup doesn't return expected value

Opened this issue · 3 comments

Hi, everyone.
I've implemented a service that sends sms to some devices by means of AWS SNS Service.
The service checks for each device if it exists in a whitelist table. If the device isn't found, the sms is sent to a fallback number.

So, I've a WhiteListUser class:

public class WhiteListUser
{
    [Key]
    public string Device { get; set; }

    public string Username { get; set; }

    public bool IsFallback { get; set; }
}

An application context :

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
    {
    }

    public DbSet<WhiteListUser> WhiteListUsers { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        builder.Entity<WhiteListUser>().HasData(new WhiteListUser
        {
            Device = "+39370XXXXXXX",
            Username = "FALLBACK",
            IsFallback = true
        });
    }
}

and the service:

public interface ISmsService
{
    Task<IList<SendSmsResponse>> SendSmsAsync(SendSms @event, CancellationToken token);
}

public class SNSService : ISmsService
{
    private readonly IAwsProvider _awsProvider;
    private readonly ILogger<SNSService> _logger;
    private readonly IHostEnvironment _environment;
    private readonly IGenericRepository<WhiteListUser> _repository;

    public SNSService(IHostEnvironment environment, IGenericRepository<WhiteListUser> repository, IAwsProvider awsProvider, ILogger<SNSService> logger)
    {
        _awsProvider = awsProvider;
        _logger = logger;
        _environment = environment;
        _repository = repository;
    }

    public async Task<IList<SendSmsResponse>> SendSmsAsync(SendSms @event, CancellationToken cancellationToken)
    {
        if (_environment.IsDevelopment())
        {
            var fallbackNumber = (await _repository.Find(u => u.IsFallback)).First().Device;
            int i = 0;
            while (i < @event.Devices.Count)
            {
                var device = @event.Devices[i];
                var users = await _repository.Find(u => u.Device == device);
                if (users?.ToList().Count == 0)
                {
                    @event.Devices[i] = fallbackNumber;
                }
                i++;
            }
        }
        var response = await _awsProvider.SendSmsAsync(@event.Devices, @event.Message, cancellationToken);
        return response;
    }
}

SendSms event is a record with a message and a list of devices:

public record SendSms
{
    public string Message { get; set; }
    public List<string> Devices { get; set; }
}

When I try to run this test, it fails:

public class SNSServiceTest
{
    private readonly ISmsService _sut;
    private readonly Mock<IAwsProvider> _awsProviderMoq;
    private readonly Mock<ILogger<SNSService>> _loggerMoq;
    private readonly Mock<IHostEnvironment> _hostEnvironmentMoq;
    private readonly Mock<IGenericRepository<WhiteListUser>> _repositoryMoq;

    public SNSServiceTest()
    {
        _awsProviderMoq = new Mock<IAwsProvider>();
        _loggerMoq = new Mock<ILogger<SNSService>>();
        _hostEnvironmentMoq = new Mock<IHostEnvironment>();
        _repositoryMoq = new Mock<IGenericRepository<WhiteListUser>>();

        _sut = new SNSService(_hostEnvironmentMoq.Object,
                              _repositoryMoq.Object,
                              _awsProviderMoq.Object,
                              _loggerMoq.Object);
    }

   

    [Fact]
    public async Task Does_Replace_Only_Not_In_Whitelist_Numbers_With_Fallback_In_Dev_Env()
    {
        // Arrange
        var @event = new SendSms
        {
            Message = "pippo",
            Devices = new List<string>
            {
                "unknown",
                WhiteListFixtures.FallbackUser.Device,
                WhiteListFixtures.WhiteListUser.Device
            }
        };

        _repositoryMoq
          .Setup(x => x.Find(c => c.IsFallback))
          .ReturnsAsync(new List<WhiteListUser> { WhiteListFixtures.FallbackUser });

        _repositoryMoq
           .Setup(x => x.Find(c => c.Device == WhiteListFixtures.FallbackUser.Device))
           .ReturnsAsync(new List<WhiteListUser> { WhiteListFixtures.FallbackUser });

        _repositoryMoq
          .Setup(x => x.Find(c => c.Device == WhiteListFixtures.WhiteListUser.Device))
          .ReturnsAsync(new List<WhiteListUser> { WhiteListFixtures.WhiteListUser });

        _hostEnvironmentMoq.Setup(x => x.EnvironmentName).Returns(Environments.Development);

        // Act
        await _sut.SendSmsAsync(@event, CancellationToken.None);

        // Assert
        _awsProviderMoq
            .Verify(x => x.SendSmsAsync(
                It.Is<IList<string>>(l => l.Count == 3 &&
                                          l[0].Equals(WhiteListFixtures.FallbackUser.Device) &&
                                          l[1].Equals(WhiteListFixtures.FallbackUser.Device) &&
                                          l[2].Equals(WhiteListFixtures.WhiteListUser.Device)),
                "pippo",
                CancellationToken.None));
    }
}

where Fixtures are:

public class WhiteListFixtures
{

public static WhiteListUser FallbackUser => new WhiteListUser
{
    Device = _fallbackNumber,
    Username = "Test1",
    IsFallback = true,
};

public static WhiteListUser WhiteListUser = new WhiteListUser
{
    Device = _whiteListNumber,
    Username = "Test2",
    IsFallback = false,
};

private static string _fallbackNumber = "+393701002134";
private static string _whiteListNumber = "+393702223344";

}

it seems that these two setups are not working, so devices are never found and all the devices are so replaced, also the third one that is in the whitelist:

        _repositoryMoq
           .Setup(x => x.Find(c => c.Device == WhiteListFixtures.FallbackUser.Device))
           .ReturnsAsync(new List<WhiteListUser> { WhiteListFixtures.FallbackUser });

        _repositoryMoq
          .Setup(x => x.Find(c => c.Device == WhiteListFixtures.WhiteListUser.Device))
          .ReturnsAsync(new List<WhiteListUser> { WhiteListFixtures.WhiteListUser });

this is the error message:

UnitTests.Services.SNSServiceTest.Does_Replace_Only_Not_In_Whitelist_Numbers_With_Fallback_In_Dev_Env
 Source: SNSServiceTest.cs line 34
 Duration: 11 ms

Message: 
Moq.MockException :
Expected invocation on the mock at least once, but was never performed: x => x.SendSmsAsync(It.Is<IList>(l => ((l.Count == 3 && l[0].Equals(WhiteListFixtures.FallbackUser.Device)) && l[1].Equals(WhiteListFixtures.FallbackUser.Device)) && l[2].Equals(WhiteListFixtures.WhiteListUser.Device)), "pippo", CancellationToken)

Performed invocations:

MockIAwsProvider:2 (x):

  IAwsProvider.SendSmsAsync(["+39370XXXXXXX", "+39370XXXXXXX", "+39370XXXXXXX"], "pippo", CancellationToken)

Stack Trace: 
Mock.Verify(Mock mock, LambdaExpression expression, Times times, String failMessage) line 330
Mock1.Verify[TResult](Expression1 expression) line 810
SNSServiceTest.Does_Replace_Only_Not_In_Whitelist_Numbers_With_Fallback_In_Dev_Env() line 67
--- End of stack trace from previous location ---

I don't know what am I doing wrong, if the test or the implementation.
I was expecting: IAwsProvider.SendSmsAsync(["+39370XXXXXXX", "+39370XXXXXXX", "+39370YYYYYYY"], "pippo", CancellationToken)

Can any expert help me?
Thanks in advance

Can someone help me???

stakx commented

@fsodano13, this issue tracker focuses on the development of Moq itself, not on fixing user code. Your query might meet a larger audience on sites such as Stack Overflow, or in Moq's Discord chat.

HI @stakx I'm not asking for debugging my code. I do not understand why
the call await _repository.Find(u => u.Device == device) doesn't return expected feature.
I want to understand if i'm not using well the library or there is some issue in that.