Search code examples
c#unit-testingmockingmoqxunit

Expected invocation on the mock exactly 5 times, but was 0 times with properly mocked arguments


By reading this post and this and I understood that Mock should be used and result should be awaited.

Nevertheless, I still get the following exception:

Moq.MockException : Expected invocation on the mock exactly 5 times, but was 0 times: _ => _.SendAsync(Mock<IClientProxy:2>.Object, It.IsAny<string>(), It.IsAny<List<Notification>>(), CancellationToken)

Performed invocations:

MockIClientContextCommand:2 (_):

IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken) IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken) IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken) IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken) IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken) IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken) IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken) IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken) IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken) IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken)

Stack Trace: Mock.Verify(Mock mock, LambdaExpression expression, Times times, String failMessage)line337 Mock1.Verify[TResult](Expression1 expression, Times times)line34 FooBarServiceTests.SendAsync_ShouldBe_Called_N_Times()line105

So unit test look like this:

private readonly Mock<IClientContextCommand> _mockClientContextCommand;

// constructor
public FooTests()
{
    _mockClientContextCommand = new Mock<IClientContextCommand>();
    _mockClientContextCommand.Setup(x => 
        x.SendAsync(_mockClientProxy.Object, It.IsAny<string>(),
                    It.IsAny<Notification[]>(), CancellationToken.None));
        
    services.AddSingleton(_mockClientContextCommand.Object);
}

[Fact]
public async Task SendAsync_ShouldBe_Called_N_Times()
{
    await _hostedService.StartAsync(CancellationToken.None);
    await Task.Delay(1);
    await _hostedService.StopAsync(CancellationToken.None);

    int times = GetNotifications().Count();
    _mockClientContextCommand.Verify(
        _ => _.SendAsync(_mockClientProxy.Object, It.IsAny<string>(), 
                         It.IsAny<List<Notification>>(), CancellationToken.None),
        Times.Exactly(times));
}

And original method looks like this:

private async Task Send(CancellationToken stoppingToken)
{
    // ... the other code is omitted for the brevity
    foreach (IGrouping<string, Notification> group in messages.GroupBy(m => m.To))
    {
        IClientProxy connection = _hubContext.Clients.User(group.Key);
        List<Notification> notifies = group.ToList();
        
        await _clientContextCommand.SendAsync(connection, 
            "FooBarNotifications", notifies, stoppingToken);
    }
}

Interface method looks like this:

public interface IClientContextCommand
{
    Task SendAsync(IClientProxy clientProxy, string method, 
                   object? arg1, CancellationToken cancellationToken = default);
}

Does anybody know what is done wrong? Any help would be greatly appreciated


Solution

  • First you need to loosen the match parameters for the mocked member using It.IsAny so that the mocked member will be invoked. Since you setup/verification does not match what was actually invoked, you get the test failure.

    Next, because this is an asynchronous member that doesn't appear to be returning a value, the member needs to return a completed task to allow the async code to flow to completion.

    //... omitted for brevity
    
    _mockClientContextCommand
        .Setup(_ => _.SendAsync(It.IsAny<IClientProxy>(), It.IsAny<string>(), It.IsAny<Notification[]>(), It.IsAny<CancellationToken>()))
        .Returns(Task.CompletedTask);
    
    //... omitted for brevity
    
    int times = GetNotifications().Count();
    _mockClientContextCommand.Verify(
        _ => _.SendAsync(It.IsAny<IClientProxy>(), It.IsAny<string>(), It.IsAny<Notification[]>(), It.IsAny<CancellationToken>()),
        Times.Exactly(times));
    

    The stack trace also shows that the expected count was supposed to be 5 but shows 10 invocations of SendAsync. This may need to be further investigated once you get the mock to match the expected invocation.