views:

790

answers:

4

I have a method that contains an asynchronous call like this:

public void MyMethod() {
    ...
    (new Action<string>(worker.DoWork)).BeginInvoke(myString, null, null);
    ...
}

I'm using Unity and creating mock objects is not a problem, but how can I test that DoWork is called without worrying about race conditions?

A previous question offers a solution, but it seems to me that wait-handles is a hack (the race condition is still there, although it should be virtually impossible to raise).


EDIT: Okay, I thought I could ask this question in a very general manner, but it seems I have to elaborate further on the problem:

I want to create a test for the above mentioned MyMethod, so I do something like this:

[TestMethod]
public void TestMyMethod() {
   ...setup...
   MockWorker worker = new MockWorker();
   MyObject myObj = new MyObject(worker);
   ...assert preconditions...
   myObj.MyMethod();
   ...assert postconditions...
}

The naïve approach would be to create a MockWorker() that simply sets a flag when DoWork has been called, and test that flag in the postconditions. This would of course lead to a race condition, where the postconditions are checked before the flag is set in the MockWorker.

The more correct approach (which I'll probably end up using) is using a wait-handle:

class MockWorker : Worker {
    public AutoResetEvent AutoResetEvent = new AutoResetEvent();

    public override void DoWork(string arg) {
        AutoResetEvent.Set();
    }
}

...and use the following assertion:

Assert.IsTrue(worker.AutoResetEvent.WaitOne(1000, false));

This is using a semaphore-like approach, which is fine... but in theory the following could happen:

  1. BeginInvoke is called on my DoWork delegate
  2. For some reason neither the main-thread or the DoWork-thread is given execution time for 1000ms.
  3. The main-thread is given execution time, and because of the timeout the assertion fails, even though the DoWork thread is yet to be executed.

Have I misunderstood how AutoResetEvent works? Am I just being too paranoid, or is there a clever solution to this problem?

+2  A: 

Wait handle would be the way I would do it. AFAIK (and I dont know for sure) asynchronous methods are using wait handles to fire off that method anyway.

I'm not sure why you think the race condition would occur, unless you are giving an abnormally short amount of time on the WaitOne call. I would put 4-5 seconds on the waitone, that way you'd know for sure if it was broken and it wasn't just a race.

Also, don't forget how wait handles work, as long as the wait handle is created, you can have the following order of execution a

  • Thread 1 - create wait handle
  • Thread 1 - set wait handle
  • Thread 2 - waitone on the wait handle
  • Thread 2 - blows through the wait handle and continues execution

Even though the normal execution is

  • Thread 1 - create wait handle
  • Thread 2 - waitone on the wait handle
  • Thread 1 - set wait handle
  • Thread 2 - blows through the wait handle and continues execution

Either works correctly, the wait handle can be set before Thread2 begins waiting on it and everything is handled for you.

Allen
Can you elaborate on this? I know the race condition is highly theoretical, but it just seems like it's something that would go wrong the day we ship ;-)
toxvaerd
I updated the post, check it out and get back to me
Allen
+3  A: 

When testing a straight delegate, just use the EndInvoke call to ensure the delegate is called and returns the appropriate value. For instance.

var del = new Action<string>(worker.DoWork);
var async = del.BeginInvoke(myString,null,null);
...
var result = del.EndInvoke(async);

EDIT

OP commented that they are trying to unit test MyMethod versus the worker.DoWork method.

In that case you will have to rely on a visible side effect from calling the DoWork method. Based on your example I can't really offer too much here because none of the inner workings of DoWork are exposed.

EDIT2

[OP] For some reason neither the main-thread or the DoWork-thread is given execution time for 1000ms.

This won't happen. When you call WaitOne on an AutoResetEvent the thread goes to sleep. It will recieve no processor time until the event is set or the timeout period expires. It's definitely pheasible that some other thread gets a significant time slice and causes a false failure. But I think that is fairly unlikely. I have several tests that run in the same manner and I don't get very many/any false failures like this. I usually pick a timeout though of ~2 minutes.

JaredPar
I'm not testing the delegate method itself - I'm testing the method invoking the delegate.
toxvaerd
Since I'm using mock objects I can pretty much choose the side effects from DoWork() myself... As I said - I'm unit testing MyMethod and NOT DoWork, so the inner workings of DoWork should be irrelevant.
toxvaerd
@Toxvaerd, You'll need to make your question clearer then. The only thing we can tell from your post is that you have a method called MyMethod that asynchronously calls a delegate and you want to know if it's called. It's impossible to help without some more detail
JaredPar
Thanks for your answers. I know it's the situation is highly unlikely, but I just wanted to know, if there was a solution out there that eliminated that last bit of uncertainty. But hey, it's a unit test. Maybe I should just relax and do it ;-) Other poster was first, so he's getting the checkmark.
toxvaerd
A: 

This is what I've done in these situations: Create a service that takes the delegate and executes it (expose this via a simple interface, inject where you call stuff asynchronously).

In the real service it will execute with BeginInvoke and thus asynchronously. Then create a version of the service for testing that invokes the delegate synchronously.

Here's an example:

public interface IActionRunner
{
   void Run(Action action, AsyncCallback callback, object obj);
   void Run<T>(Action<T> action, T arg, AsyncCallback callback, object obj);
   void Run<T1, T2>(Action<T1, T2> action, T1 arg1, T2 arg2, AsyncCallback callback, object obj);
   void Run<T1, T2, T3>(Action<T1, T2, T3> action, T1 arg1, T2 arg2, T3 arg3, AsyncCallback callback, object obj);
   void Run<T1, T2, T3, T4>(Action<T1, T2, T3, T4> action, T1 arg1, T2 arg2, T3 arg3, T4 arg4, AsyncCallback callback, object obj);
}

An Asycnhronous implementation of this service looks like this:

public void Run<T>(Action<T> action, T arg, AsyncCallback callback, object obj)
{
   action.BeginInvoke(arg, callback, obj);
}

A Synchronous implementation of this service looks like this:

public void Run<T>(Action<T> action, T arg, AsyncCallback callback, object obj)
{
   action(arg);
}

When you are unit testing, if you're using something like RhinoMocks and the AutoMocking container, you can swap in your Synchronous ActionRunner:

_mocks = new MockRepository();

_container = new AutoMockingContainer(_mocks);
_container.AddService(typeof(IActionRunner), new SyncActionRunner());
_container.Initialize();

The ActionRunner doesn't need to be tested; it's just a thin veneer over the method call.

Chris Holmes
Won't this just move the problem? What happens when I want to test the service?
toxvaerd
I personally don't worry about testing features of .Net. I kind of expect the asynch call to work out of the box, if you get what I'm saying. Testing BeginInvoke() doesn't seem like a very useful test to me.
Chris Holmes
Service is a thin wrapper; I'm going to edit my post so you can see.
Chris Holmes
A: 

Can you please elaborate what you mean by "...make myObj use the MockWorker...", in your TestMyMethod code. I am working on something similar and was trying to use the solution given by you here. But I do not seem to get this line of code.

Normally I inject these kind of dependencies via the constructor, so in the example that would be something like:MyObject myObj = new MyObject(worker);
toxvaerd
But since I wrote this post I started using Moq for making mock-objects - it is very cool! Check out http://code.google.com/p/moq/
toxvaerd