How to Unit Test BeginInvoke on an Action
Asked Answered
S

3

8

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";
     }  
}
Suzerainty answered 4/5, 2011 at 15:35 Comment(0)
D
9

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.

Dibrin answered 4/5, 2011 at 15:49 Comment(3)
Sounds good, have thought about it and can use it for new code, but how do I unit test existing code which calls BeginInvoke directly.Suzerainty
Refactor? The dependency exists there; the only way to unit test it is to either abstract it, or not test it. =/Dibrin
the callback parameter has to be of type AsyncCallback. But thank you, this is great :)Tanguy
B
2

BeginInvoke() returns an IAsyncResult which you can use to wait.

IAsynchResult ar = action.BeginInvoke(...);
ar.AsyncWaitHandle.WaitOne();
Bidarka answered 4/5, 2011 at 15:43 Comment(4)
How do I get hold of IAsynchResult (ar)?Suzerainty
it's the return value from BeginInvoke. You can see that in the code.Bidarka
I am invoking it from the constructor and don't have access to it from outside the class to write unit tests on.Suzerainty
sometimes you just have to refactor your code to make it testableBidarka
C
0

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.

Christmann answered 4/5, 2011 at 15:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.