Description
Describe the Bug
I want to use Mock.Protected().Verify
to check that my function (WatchdogInternalAsync
) has been executed.
If I run the unit test (StartWatchdog_WatchdogInternal_FunctionIsCalled1
) individually, the test is successful.
However, if another test runs before it, the test fails.
If the same test (StartWatchdog_WatchdogInternal_FunctionIsCalled2
) is run again afterward, it only fails sometimes.
And if I call my function twice (in StartWatchdog_WatchdogInternal_FunctionIsCalled3
) and check for Times.Exactly(2)
, then this test is always successful.
I have the feeling it is a timing problem.
When I change the names of the test functions, causing them to run in a different order, I sometimes get other tests failing.
Steps to Reproduce
- Run all tests
Expected Behavior
All unit tests should be successful
Exception with Stack Trace
Moq.MockException :
Expected invocation on the mock once, but was 0 times: mock => mock.WatchdogInternalAsync()
Performed invocations:
Mock<AbstractGrpcClientConnectionWithWatchdog<ApiService.ApiServiceClient>:3> (mock):
AbstractGrpcClientConnectionWithWatchdog<ApiService.ApiServiceClient>.WatchdogInternalAsync()
Version Info
- Moq 4.20.70
- NUnit 4.1.0
Code
Project:
MoqAbstractProtected.zip
C# Code
#nullable disable
using NUnit.Framework;
using Moq;
using Moq.Protected;
namespace MoqAbstractProtected;
public class Tests
{
private const string WatchdogInternalAsync = "WatchdogInternalAsync";
private Mock<AbstractClass> ConnectionMock;
private AbstractClass Connection;
[SetUp]
public void Setup()
{
ConnectionMock = new Mock<AbstractClass>();
ConnectionMock.CallBase = true;
Connection = ConnectionMock.Object;
ConnectionMock.Protected()
.Setup<Task>(WatchdogInternalAsync)
.Callback(() => Console.Out.WriteLine($"\"{WatchdogInternalAsync}\" was called!"))
.Returns(Task.CompletedTask);
}
[Test]
public void StartWatchdog_InitNotCalled_ThrowsException()
{
// Prepare
// Connection.Init(); NO init call
// Test
Exception exception = Assert.Throws<Exception>(
delegate
{
// ReSharper disable once AssignNullToNotNullAttribute
Connection.StartWatchdog();
});
// Assert
Assert.That(exception, Is.Not.Null);
Assert.That(exception.Message, Is.EqualTo("The Init function must be called first."));
}
[Test]
public void StartWatchdog_ConnectionIsDisposed_WatchdogInternalNotCalled()
{
// Prepare
Connection.Init();
Connection.Dispose();
// Test
Assert.DoesNotThrow(() => { Connection.StartWatchdog(); });
// Assert
ConnectionMock.Protected()
.Verify(WatchdogInternalAsync, Times.Never());
}
[Test]
public void StartWatchdog_WatchdogInternal_FunctionIsCalled1()
{
// Prepare
Connection.Init();
// Test
Connection.StartWatchdog();
// Assert
ConnectionMock.Protected()
.Verify(WatchdogInternalAsync, Times.Once());
}
[Test]
public void StartWatchdog_WatchdogInternal_FunctionIsCalled2()
{
// Prepare
Connection.Init();
// Test
Connection.StartWatchdog();
// Assert
ConnectionMock.Protected()
.Verify(WatchdogInternalAsync, Times.Once());
}
[Test]
public void StartWatchdog_WatchdogInternal_FunctionIsCalled3()
{
// Prepare
Connection.Init();
// Test
Connection.StartWatchdog();
Connection.StartWatchdog();
// Assert
ConnectionMock.Protected()
.Verify(WatchdogInternalAsync, Times.Exactly(2));
}
}
namespace MoqAbstractProtected;
public abstract class AbstractClass : IDisposable
{
private readonly CancellationTokenSource CancellationTokenSource;
protected CancellationToken CancellationToken { get; }
protected bool InitDone { get; private set; }
protected AbstractClass()
{
CancellationTokenSource = new CancellationTokenSource();
CancellationToken = CancellationTokenSource.Token;
}
public void Init()
{
Console.Out.WriteLine("Init");
if (InitDone)
{
throw new Exception("Init already called. Dispose this instance and create a new one.");
}
InitDone = true;
}
public void StartWatchdog()
{
if (InitDone is false)
{
throw new Exception($"The {nameof(Init)} function must be called first.");
}
Task.Factory.StartNew(WatchdogAsync, CancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
private async Task WatchdogAsync()
{
if (CancellationToken.IsCancellationRequested)
{
return;
}
await WatchdogInternalAsync(); // This is a blocking call
Console.Out.WriteLine("Watchdog has stopped monitoring. Wuff!");
}
protected abstract Task WatchdogInternalAsync();
protected virtual void Dispose(bool disposing)
{
Console.Out.WriteLine($"Dispose({disposing})");
if (disposing)
{
CancellationTokenSource.Cancel();
CancellationTokenSource.Dispose();
}
}
public void Dispose()
{
Dispose(true);
Console.Out.WriteLine("Dispose");
GC.SuppressFinalize(this);
}
}