I am looking for a way to test BeginInvoke on an Action method, since the method runs on a background thread there is no way of knowing when it actually completes or calls callback method. I am looking for a way to keep my test wait until the callback gets called before making assertions.
In the following Presenter class, you can notice that I am invoking PopulateView on background thread which updates the view when data is fetched and I am trying assert the view Properties are correctly initialized.
I am using NUnit and Moq.
public class Presenter
{
private IView _view;
private IService _service;
public Presenter(IView view, IService service)
{
_view = view;
_service = service;
Action action = PopulateView;
action.BeginInvoke(PopulateViewCallback, action);
}
private void PopulateViewCallback(IAsyncResult ar)
{
try
{
Action target = (Action)ar.AsyncState;
ta开发者_Go百科rget.EndInvoke(ar);
}
catch (Exception ex)
{
Logger.Instance.LogException("Failed to initialize view", ex);
}
}
private void PopulateView()
{
Thread.Sleep(2000); // Fetch data _service.DoSomeThing()
_view.Property1 = "xyz";
}
}
Abstract your code so that you can inject the behavior you want at testing time.
public class MethodInvoker
{
public virtual void InvokeMethod(Action method, Action callback)
{
method.BeginInvoke(callback, method);
}
}
This version is asynchronous. At testing time, you can simply make a blocking version:
public class TestInvoker
{
public IAsyncResult MockResult { get; set; }
public override void InvokeMethod(Action method, Action callback)
{
method();
callback(MockResult);
}
}
Then your code simply changes to this:
// Inject this dependency
Invoker.InvokeMethod(PopulateView, PopulateViewCallback);
At runtime, it's asynchronous. At testing time, it blocks the call.
BeginInvoke()
returns an IAsyncResult
which you can use to wait.
IAsynchResult ar = action.BeginInvoke(...);
ar.AsyncWaitHandle.WaitOne();
You don't need to check that methods are called instead test the end result - in this case that _view.Propert1 == "xyz".
Because it's an async call you might need to have a loop that Asserts periodically that the value has been set, also a timeout on the test or the check is a must otherwise your test would never fail (just stuck).
You might consider stubbing out the action (PopulateView) in order to skip the Sleep.
精彩评论