I'm unit testing some asynchronous code. I have tried to abstract it to make the issue more clear. My issue is that I want to set up the mocked Bar
to execute Foo
's private callback method after BeginWork
returns. The callback is supposed to call Set()
on the ManualResetEvent
allowing the calling thread to continue to run. When I run the test my thread blocks indefinitely at the call to WaitOne()
.
Code under test:
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 Code:
[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());
}
First observation was that you pass in a mocked ISocket
in state
and try to cast it to Socket
in async callback which will result in a null error which means connectDone.Set()
is never called so WaitOne
will not unblock.
Change that to
private void ConnectCallback(IAsyncResult result) {
ISocket client = (ISocket)result.AsyncState;
client.EndConnect(result);
connectDone.Set();
}
Second observation was that you were not setting up the mocked calls correctly. No need for reflection here as you needed to get the passed arguments from the mock and invoke then in the mock callback setup
The following is based on your original code. Review it to get an understanding of what was explained above.
[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);
}
}
Finally I believe you should consider changing this code to use TPL to get around the whole call back and IAsyncResult
drama. Basically exposing an async API and wrapping the calls with a TaskCompletionSource<T>
but I guess that is outside of the scope of this question.