使用Moq模拟单元测试中的执行延迟

时间:2013-04-04 20:09:03

标签: c# unit-testing moq mstest

我正在尝试测试以下内容:

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;
}

进行以下单元测试:

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()只返回一个字符串,“Delay Delivery”方法是

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;
}

我使用Moq作为模拟框架,使用MSTest作为测试框架。每当我运行单元测试时,我得到以下输出:

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

除了上面方法中使用Thread.Sleep的明显“代码味道”之外,单元测试的结果并不是我想要完成的。

任何人都可以建议使用Moq框架来更好/准确地模拟邮件“传递”的延迟。我遗漏了一些“胶水”代码,只包括相关部分。如果我遗漏的某些内容阻止您理解这个问题,请告诉我。

6 个答案:

答案 0 :(得分:32)

如果你想让Moq模拟器暂时不做任何操作,你可以使用回调:

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);

我在LinqPad中尝试过,如果调整Thread.Sleep(),执行时间也会相应变化。

答案 1 :(得分:7)

当您设置模拟时,您可以告诉线程在返回函数中休眠:

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

myService.Setup(x => x.GetResultDelayed()).Returns(() => {
    Thread.Sleep(100);
    return "result";
});

答案 2 :(得分:4)

如果运行异步代码,Moq 可以通过 TimeSpan 使用第二个参数延迟响应

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

如果每次调用方法都需要指定不同的延迟,可以使用.SetupSequence之类的

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

答案 3 :(得分:1)

我无法让Moq版本工作,所以我最终做了这样的事情:

使用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();

    }

}

答案 4 :(得分:1)

我喜欢并投票支持serup的解决方案。我的答案是他转换后的一个版本,可用作图书馆。

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;
    }
}

答案 5 :(得分:1)

我有一个类似的情况,但是使用了异步方法。对我有用的是执行以下操作:

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