Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mock.Protected().Verify fails sometimes #1464

Open
TheBigNeo opened this issue Mar 11, 2024 · 0 comments
Open

Mock.Protected().Verify fails sometimes #1464

TheBigNeo opened this issue Mar 11, 2024 · 0 comments
Labels

Comments

@TheBigNeo
Copy link

TheBigNeo commented Mar 11, 2024

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);
    }
}
@TheBigNeo TheBigNeo added the bug label Mar 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant