使用moq模拟使用异步回调的方法

时间:2019-06-28 14:59:58

标签: c# unit-testing nunit moq iasyncresult

我正在测试一些异步代码。我试图对其进行抽象,以使问题更加清楚。我的问题是我想设置模拟的Bar以在Foo返回后执行BeginWork的私有回调方法。该回调应该在Set()上调用ManualResetEvent,从而允许调用线程继续运行。当我运行测试时,线程在调用WaitOne()时无限期阻塞。

正在测试的代码:

using System.Threading;
using NUnit.Framework;
using Moq;
using System.Reflection;

public interface IBar
{
    int BeginWork(AsyncCallback callback);
}

public class Bar : IBar
{
    public int BeginWork(AsyncCallback callback)
    {
        // do stuff
    }   // execute callback
}

public class Foo
{
    public static ManualResetEvent workDone = new ManualResetEvent(false);
    private IBar bar;

    public Foo(IBar bar)
    {
        this.bar = bar;
    }

    public bool DoWork()
    {
        bar.BeginWork(new AsyncCallback(DoWorkCallback));
        workDone.WaitOne(); // thread blocks here
        return true;
    }

    private void DoWorkCallback(int valueFromBeginWork)
    {
        workDone.Set();
    }
}

测试代码:

[Test]
public void Test()
{
    Mock<IBar> mockBar = new Mock<IBar>(MockBehavior.Strict);

    // get private callback
    MethodInfo callback = typeof(Foo).GetMethod("DoWorkCallback", 
                  BindingFlags.Instance | BindingFlags.NonPublic);

    mockBar.Setup(() => BeginWork(It.IsAny<AsyncCallback>())) 
                        .Returns(0).Callback(() => callback.Invoke(0));

    Foo = new Foo(mockBar.Object);
    Assert.That(Foo.DoWork());
}

1 个答案:

答案 0 :(得分:0)

首先观察到您在ISocket中传递了一个模拟的state并尝试在异步回调中将其强制转换为Socket,这将导致空错误,这意味着{{1} }永远不会被调用,因此connectDone.Set()不会解除阻止。

将其更改为

WaitOne

第二个观察结果是您没有正确设置模拟呼叫。这里不需要反思,因为您需要从模拟获取传递的参数,然后在模拟回调设置中调用

以下内容基于您的原始代码。对其进行回顾,以了解上面的解释。

private void ConnectCallback(IAsyncResult result) {
    ISocket client = (ISocket)result.AsyncState;
    client.EndConnect(result);
    connectDone.Set();
}

最后,我相信您应该考虑更改此代码以使用TPL来解决整个回拨和[TestClass] public class SocketManagerTests { [TestMethod] public void ConnectTest() { //Arrange var mockSocket = new Mock<ISocket>(); //async result needed for callback IAsyncResult mockedIAsyncResult = Mock.Of<IAsyncResult>(); //set mock mockSocket.Setup(_ => _.BeginConnect( It.IsAny<EndPoint>(), It.IsAny<AsyncCallback>(), It.IsAny<object>()) ) .Returns(mockedIAsyncResult) .Callback((EndPoint ep, AsyncCallback cb, object state) => { var m = Mock.Get(mockedIAsyncResult); //setup state object on mocked async result m.Setup(_ => _.AsyncState).Returns(state); //invoke provided async callback delegate cb(mockedIAsyncResult); }); var manager = new SocketManager(mockSocket.Object); //Act var actual = manager.Connect(); //Assert Assert.IsTrue(actual); mockSocket.Verify(_ => _.EndConnect(mockedIAsyncResult), Times.Once); } } 剧情。基本上公开一个异步API并使用IAsyncResult包装调用,但是我想这不在此问题的范围内。