Mock.Protected().Verify fails sometimes
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);
}
}
Due to lack of recent activity, this issue has been labeled as 'stale'. It will be closed if no further activity occurs within 30 more days. Any new comment will remove the label.
Still open
