[Source: http://geekswithblogs.net/EltonStoneman]
There are a few situations when you're unit testing a component which calls out to another worker – a SQL insert from a separate thread, or a file drop from an asynchronous service call. You want to verify that the worker's completed correctly, but don't want to make the test run any longer than needed. Previously I've used bespoke code in tests where this was required, but the number has been increasingly recently so I've added a RetryAssert method to our fixture base class, for tests to use like this:
this.RetryAssert(1, 10, "File not found", new Assertion(delegate() { return File.Exists("c:\\temp.txt"); }));
RetryAssert takes an Assertion delegate (an anonymous one in this example) and repeatedly invokes it, pausing for the specified retry interval between calls, until the specified timeout is reached or the assertion returns true. At the first true assertion, the test is passed. If the assertion is false for every call until the timeout is reached, the test is failed.
The code is straightforward, but handy to have in your base class:
/// <summary>
/// Runs the given assertion repeatedly, until it returns true or the operation times out.
/// </summary>
/// <remarks>
/// If the assertion returns true at any point, the call issues passes the test; if the assertion
/// fails every point up to the timeout, the call issues Assert.Fail.
/// </remarks>
/// <param name="retryInterval">Interval between assertion calls, in seconds</param>
/// <param name="timeout">Tiemout before test is failed, in seconds</param>
/// <param name="failureMessage">Message to write if test fails</param>
/// <param name="assertion">Assertion delegate to run</param>
protected void RetryAssert(int retryInterval, int timeout, string failureMessage, Assertion assertion)
{
bool assert = false;
this.StartAssertTimer(timeout * 1000);
while (!assert && !this._assertTimeoutReached)
{
assert = assertion();
if (assert)
{
this.StopAssertTimer();
break;
}
else
{
Thread.Sleep(retryInterval * 1000);
}
}
Assert.IsTrue(assert, failureMessage);
}
protected delegate bool Assertion();
private bool _assertTimeoutReached;
private System.Timers.Timer _assertTimer;
private void StartAssertTimer(double timeoutMilliseconds)
{
this._assertTimeoutReached = false;
this._assertTimer = new System.Timers.Timer(timeoutMilliseconds);
this._assertTimer.Start();
this._assertTimer.Elapsed += new ElapsedEventHandler(assertTimer_Elapsed);
}
private void assertTimer_Elapsed(object sender, ElapsedEventArgs e)
{
this.StopAssertTimer();
this._assertTimeoutReached = true;
}
private void StopAssertTimer()
{
this._assertTimer.Stop();
this._assertTimer = null;
}
Note that this is a straightforward example and isn't threadsafe, but is fine for the majority of cases.