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 Mock
1.Verify[TResult](Expression
1 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
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.