简体   繁体   中英

How to Unit Test BeginInvoke on an Action

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;
                target.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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM