[Test]
public void SlowAndFragileTest()
{
// Setup
dataProviderMock = MockRepository.
GenerateMock<IDataProvider>();
systemUnderTest = new PollCommand(dataProviderMock);
// Act
systemUnderTest.StartPoll();
// Make sure the timer has been executed at least
// once. The action is started after 10 seconds.
Thread.Sleep(11000);
// Assert
dataProviderMock .AssertWasCalled(
x=>x.Retrieve());
}
This testing approach is problematic. The test always takes 11 seconds, which is far to slow for a unit test. Depending on the timer used it is not guaranteed at what exact point in time the action is triggered. The action itself also takes time, so we can't be 100% sure that the action is finished when we test reaches the assert statement.
Solution - Test code with timers in one thread
We can separate the timer mechanics from our code and use a special timer which executes the action on the current thread. We can also tell this timer how much time has passed by:[Test]
public void ShouldRetrieveDataEvery10Seconds()
{
// Arrange
timerStub = new DeterministicTimer();
dataProviderMock = MockRepository.
GenerateMock<IDataProvider>();
systemUnderTest = new PollCommand(
timerStub,
dataProviderMock);
// Act
systemUnderTest.StartPoll();
// Now execute the timer actions on
// the current thread.
timerStub.TickSeconds(30);
// Assert
dataProviderMock.AssertWasCalled(
x=>x.Retrieve(),
y=>y.Repeat.Times(3));
}
This test is very fast and takes fractions of a second. This test is also robust because every code is executed on the current thread.
Our PollComand class we are testing now looks very simple:
private readonly ITimer timer;
public void StartPoll()
{
timer.StartTimer(
RetrieveData,
new TimeSpan(0, 0, 0, 10));
}
private void RetrieveData()
{
retrievedDate = dataProvider.Retrieve();
}
The DeterministicTimer is straight forward:
public class DeterministicTimer : ITimer
{
private Action action;
private TimeSpan intervall;
private bool isStarted;
private TimeSpan elapsedTime;
#region ITimer methods
public void StartTimer(Action action, TimeSpan intervall)
{
isStarted = true;
this.action = action;
this.intervall = intervall;
elapsedTime = new TimeSpan();
}
public bool IsStarted()
{
return isStarted;
}
public void StopTimer()
{
isStarted = false;
}
#endregion
public void TickSeconds(int seconds)
{
TimeSpan newElapsedTime = elapsedTime + new TimeSpan(0, 0, 0, seconds);
if (isStarted)
{
long executionCountBefore = elapsedTime.Ticks / intervall.Ticks;
long executionCountAfter = newElapsedTime.Ticks / intervall.Ticks;
for (int i = 0; i < executionCountAfter - executionCountBefore; i++)
{
action();
}
}
elapsedTime = newElapsedTime;
}
}
See also my MSDN Magazine article for general remarks about unit testing multithreaded code.