Reputation: 3822
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:
Have I misunderstood how AutoResetEvent works? Am I just being too paranoid, or is there a clever solution to this problem?
Upvotes: 5
Views: 5131
Reputation: 11594
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.
Upvotes: 2
Reputation: 755457
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.
Upvotes: 3
Reputation: 19446
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
Even though the normal execution is
Either works correctly, the wait handle can be set before Thread2 begins waiting on it and everything is handled for you.
Upvotes: 3