Thursday, March 12, 2009

xUnit tests and threads

OK, it goes against the idea of unit testing a little bit, but sometimes you can only get some code to fail by having another thread attempt to change a shared value. In my case, I hade a piece of code like this:

var thread = new Thread(() => Assert.NotNull(null));
thread.Start();
thread.Join();



This block of code starts a thread and checks that the value, null, is not null. The xUnit Assert will throw an exception once the thread is running. What happens to uncaught exceptions in threads? In the old 1.0 and 1.1 days the thread would terminate silently. It turns out that this is what this code will do if you attempt to run it in TestDriven.Net. In other words, the test will not fail!



To get this to work the Assert needs to be surrounded by a try/catch block.




var thread = new Thread(SafeAssertBlock);
thread.Start();
thread.Join();

...

public void SafeAssertBlock() {
try {
Assert.NotNull(null);
}
catch (Exception ex) {
// log the exception or pass it back to the owning thread...
}
}



What I eventually settled on was a form like this.




private Exception ThreadsException { get; set; }

...

ExceptionCatcher(() => Assert.Null(ContextScope<string>.GetInstance(ScopeKey)));

...

private void ExceptionCatcher(Action action)
{
try
{
ThreadsException = null;
action();
}
catch (Exception ex)
{
Logger.Error(ex.GetBaseException().Message);
ThreadsException = ex;
}
}



This passes the Assertion block into the ExceptionCatcher, which in turn sets a variable named ThreadsException back in the main thread. If ThreadsException is not null after we make the call then we can log that exception or re-throw it.



So, what happens to xUnit if you attempt to test code that throws an exception in a thread? It turns out that each test is run in its own AppDomain and when the exception is thrown there is an attempt to Serialize the exception. I'm not totally sure, but I believe that communication between AppDomains uses .NET Remoting. In any case, you get an ugly Marshalling exception down in the guts. This may have been helped if the xUnit guys had decided to have their Exception types be Serializable and have the appropriate serialization constructors. Perhaps in a later release?

No comments:

Post a Comment