[英]Mocking a method that uses an asynchronous callback with moq
我正在測試一些異步代碼。 我試圖將其抽象化以使問題更加清晰。 我的問題是我想在BeginWork
返回后設置BeginWork
Bar
來執行Foo
的私有回調方法。 回調應該在ManualResetEvent
上調用Set()
,允許調用線程繼續運行。 當我運行測試時,我的線程無限期地阻塞對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());
}
第一個觀察是你傳入一個ISocket
state
並嘗試在異步回調中將其connectDone.Set()
為Socket
,這將導致null錯誤,這意味着從不調用connectDone.Set()
因此WaitOne
不會解除阻塞。
改為
private void ConnectCallback(IAsyncResult result) {
ISocket client = (ISocket)result.AsyncState;
client.EndConnect(result);
connectDone.Set();
}
第二個觀察是你沒有正確設置模擬的調用。 這里不需要反射,因為你需要從模擬中獲取傳遞的參數然后在模擬回調設置中調用
以下內容基於您的原始代碼。 檢查它以了解上面解釋的內容。
[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);
}
}
最后我相信您應該考慮更改此代碼以使用TPL來繞過整個回調和IAsyncResult
戲劇。 基本上暴露異步API並使用TaskCompletionSource<T>
包裝調用,但我想這不屬於這個問題的范圍。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.