28

I'm trying to test the following:

protected IHealthStatus VerifyMessage(ISubscriber destination)
{
    var status = new HeartBeatStatus();

    var task = new Task<CheckResult>(() =>
    {
        Console.WriteLine("VerifyMessage(Start): {0} - {1}", DateTime.Now, WarningTimeout);
        Thread.Sleep(WarningTimeout - 500);
        Console.WriteLine("VerifyMessage(Success): {0}", DateTime.Now);
        if (CheckMessages(destination))
        {
            return CheckResult.Success;
        }

        Console.WriteLine("VerifyMessage(Pre-Warning): {0} - {1}", DateTime.Now, ErrorTimeout);
        Thread.Sleep(ErrorTimeout - 500);
        Console.WriteLine("VerifyMessage(Warning): {0}", DateTime.Now);
        if (CheckMessages(destination))
        {
            return CheckResult.Warning;
        }

        return CheckResult.Error;
    });

    task.Start();

    task.Wait();
    status.Status = task.Result;

    return status;
}

with the following unit test:

public void HeartBeat_Should_ReturnWarning_When_MockReturnsWarning()
{
    // Arrange
    var heartbeat = new SocketToSocketHeartbeat(_sourceSubscriber.Object, _destinationSubscriber.Object);
    heartbeat.SetTaskConfiguration(this.ConfigurationHB1ToHB2_ValidConfiguration());

    // Simulate the message being delayed to destination subscriber.
    _destinationSubscriber.Setup(foo => foo.ReceivedMessages).Returns(DelayDelivery(3000, Message_HB1ToHB2()));

    // Act
    var healthStatus = heartbeat.Execute();

    // Assert
    Assert.AreEqual(CheckResult.Warning, healthStatus.Status);
}

Message_HB1ToHB2() just returns a string of characters and the "Delay Delivery" method is

private List<NcsMessage> DelayDelivery(int delay, string message)
{
    var sent = DateTime.Now;
    var msg = new NcsMessage()
    {
        SourceSubscriber = "HB1",
        DestinationSubscriber = "HB2",
        SentOrReceived = sent,
        Message = message
    };

    var messages = new List<NcsMessage>();
    messages.Add(msg);

    Console.WriteLine("DelayDelivery: {0}", DateTime.Now);
    Thread.Sleep(delay);
    Console.WriteLine("DelayDelivery: {0}", DateTime.Now);

    return messages;
}

I'm using Moq as the mocking framework and MSTest as the testing framework. Whenever I run the unit test, I get the following output:

DelayDelivery: 04/04/2013 15:50:33
DelayDelivery: 04/04/2013 15:50:36
VerifyMessage(Start): 04/04/2013 15:50:36 - 3000
VerifyMessage(Success): 04/04/2013 15:50:38

Beyond the obvious "code smell" using the Thread.Sleep in the methods above, the result of the unit test is not what I'm trying to accomplish.

Can anyone suggest a better/accurate way to use the Moq framework to simulate a delay in "delivery" of the message. I've left out some of the "glue" code and only included the relevant parts. Let me know if something I've left out that prevents you from being able to understand the question.

Ryan Gates
  • 4,501
  • 6
  • 50
  • 90
Julian Easterling
  • 830
  • 1
  • 10
  • 21
  • 3
    You'd be better off creating an example someone (other than you) can actually run that highlights what you're trying to accomplish. There's nothing here someone could copy and run without doing a bunch of extra work. – Austin Salonen Apr 04 '13 at 20:34

6 Answers6

55

If you want a Moq mock to just sit and do nothing for a while you can use a callback:

Mock<IFoo> mockFoo = new Mock<IFoo>();
mockFoo.Setup(f => f.Bar())
       .Callback(() => Thread.Sleep(1000))
       .Returns("test");

string result = mockFoo.Object.Bar(); // will take 1 second to return

Assert.AreEqual("test", result);

I've tried that in LinqPad and if you adjust the Thread.Sleep() the execution time varies accordingly.

Adam Rodger
  • 3,472
  • 4
  • 33
  • 45
  • The Callback() method was what I was missing... I updated the unit test to use that and the unit test now "mocks" the delay correctly... Thanks! – Julian Easterling Apr 05 '13 at 13:06
  • 8
    this doesn't seem to work for me, I think the callback is executed after the result is returned by the Moq – Dan Dinu May 19 '16 at 14:09
  • 1
    I concur with @DanDinu - if you run two simultaneous calls to `Bar()` then the two `Callback`s are executed *after* both `Bar()`s complete. – oatsoda Jun 22 '16 at 09:57
  • 1
    For those who are not able to get this to work: The callback will only be executed before the mocked method, if you mock the callback before you mock the return call (as per the [Moq documentation "callbacks can be specified before and after invocation"](https://github.com/Moq/moq4/wiki/Quickstart)). If you are mocking a void method, then I'm afraid you're out of luck, as the callback will always be executed after the mocked method executes. – Doctor Jones Jun 20 '18 at 10:01
  • 1
    1000 is a long time for a test to run. This should be setup to run until it is told to stop. Consider replacing Thread.Sleep with Task.Delay – Jacob Brewer Oct 29 '18 at 18:27
20

If running asynchronous code, Moq has the ability to delay the response with the second parameter via a TimeSpan

mockFooService
    .Setup(m => m.GetFooAsync())
    .ReturnsAsync(new Foo(), TimeSpan.FromMilliseconds(500)); // Delay return for 500 milliseconds.

If you need to specify a different delay each time the method is called, you can use .SetupSequence like

mockFooService
    .SetupSequence(m => m.GetFooAsync())
    .Returns(new Foo())
    .Returns(Task.Run(async () => 
    {
        await Task.Delay(500) // Delay return for 500 milliseconds.
        return new Foo();
    })

Ben Sampica
  • 2,912
  • 1
  • 20
  • 25
17

When you setup your mock you can tell the thread to sleep in the return func:

Mock<IMyService> myService = new Mock<IMyService>();

myService.Setup(x => x.GetResultDelayed()).Returns(() => {
    Thread.Sleep(100);
    return "result";
});
Susanne Peng
  • 867
  • 1
  • 10
  • 16
  • 2
    Exactly. And this has the advantage over the accepted solution that it allows you to run it as async if GetResultDelayed is e.g of type Task: myService.Setup(x => x.GetResultDelayed()).Returns(async () => { await Task.Delay(100); return "result"; }); – Jesper Mygind Feb 04 '19 at 10:18
3

I could not get Moq version to work, so I ended up making something like this:

a small example using WaitHandle:

[TestFixture]
public class EventWaitHandleTests
{
    class Worker {
        private volatile bool _shouldStop;
        public EventWaitHandle WaitHandleExternal;

        public void DoWork ()
        {
            while (!_shouldStop)
            {
                Console.WriteLine("worker thread: working...");
                Thread.Sleep(1000);
                WaitHandleExternal.Set();
            }
        }

        public void RequestStop()
        {
            _shouldStop = true;
        }

    }

    [Test]
    public void WaitForHandleEventTest()
    {
        EventWaitHandle _waitHandle = new AutoResetEvent (false); // is signaled value change to true

        // start a thread which will after a small time set an event
        Worker workerObject = new Worker ();
        workerObject.WaitHandleExternal = _waitHandle;
        Thread workerThread = new Thread(workerObject.DoWork);

        // Start the worker thread.
        workerThread.Start();

        Console.WriteLine ("Waiting...");
        _waitHandle.WaitOne();                // Wait for notification
        Console.WriteLine ("Notified");

        // Stop the worker thread.
        workerObject.RequestStop();

    }

}
serup
  • 3,676
  • 2
  • 30
  • 34
2

I like and voted for serup's solution. My answer is a version of his converted for use as a library.

using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;

/// <summary>
/// support halting a workflow and waiting for a finish request
/// </summary>
public class MockWorker
{
    private readonly DateTime? _end;
    private volatile bool _shouldStop;

    /// <summary>
    /// Create a worker object
    /// </summary>
    /// <param name="timeoutInMilliseconds">How long before DoWork will timeout.  default - Null will not timeout.</param>
    public MockWorker(int? timeoutInMilliseconds = null)
    {
        if (timeoutInMilliseconds.HasValue)
            _end = DateTime.Now.AddMilliseconds(timeoutInMilliseconds.Value);
    }

    /// <summary>
    /// Instruct DoWork to complete
    /// </summary>
    public void RequestStop()
    {
        _shouldStop = true;
    }

    /// <summary>
    /// Do work async will run until either timeoutInMilliseconds is exceeded or RequestStop is called.
    /// </summary>
    public async Task DoWorkAsync()
    {
        while (!_shouldStop)
        {
            await Task.Delay(100);
            if (_end.HasValue && _end.Value < DateTime.Now)
                throw new AssertFailedException("Timeout");
        }
    }

    /// <summary>
    /// Do work async will run until either timeoutInMilliseconds is exceeded or RequestStop is called.
    /// </summary>
    /// <typeparam name="T">Type of value to return</typeparam>
    /// <param name="valueToReturn">The value to be returned</param>
    /// <returns>valueToReturn</returns>
    public async Task<T> DoWorkAsync<T>(T valueToReturn)
    {
        await DoWorkAsync();
        return valueToReturn;
    }
}
Jacob Brewer
  • 2,574
  • 1
  • 22
  • 25
1

I had a similiar situation, but with an Async method. What worked for me was to do the following:

 mock_object.Setup(scheduler => scheduler.MakeJobAsync())
  .Returns(Task.Run(()=> { Thread.Sleep(50000); return Guid.NewGuid().ToString(); }));