Unit testing a class that uses a Timer
I've got a class that has a private member that has for type System.Windows.Forms.Timer
. There's also a private method that is being called every time my timer ticks.
- Is it worth testing the method? (since it's private)
- How can I test it? (I know I can have my test class inheriting the class I want to test...)
- Should I be mocking my timer? Because if I have to test a class that uses an internal timer, my tests may take a lot of time to complete, right?
edit:
Actually, the method has a dependency on timing, here's the code:
private void alertTick(object sender, EventArgs e) {
if (getRemainingTime().Seconds <= 0) {
Display.execute(Name, WarningState.Ending, null);
AlertTimer.Stop();
}
else {
var warning = _warnings.First(x => x == getRemainingTime());
if (warning.TotalSeconds > 0)
Display.execute(Name, WarningState.Running, warning);
}
}
As you can see, if the timer is running, it calls Display.execute()
with different parameters from when it's ending (when the remaining time equals 0). Would that be a problem of design?
Solution 1:
- You are not testing methods (private or public) - you are verifying behavior of your class. And if you have not verified some behavior, then you can't tell it was implemented. There are several ways this behavior could be invoked - public interface of your class, or some event in dependency. Also not necessary that behavior invocation will change something reached by the public interface, interactions with dependencies also matter.
- See example below - it shows how to test such "hidden" behavior.
- See example below - it shows how to split responsibilities, inject dependencies and mock them.
Actually your class have too many responsibilities - one is scheduling some task, and another - executing some actions. Try to split your class into two separate classes with single responsibilities.
So, scheduling goes to scheduler :) API of scheduler could be like:
public interface IScheduler
{
event EventHandler<SchedulerEventArgs> Alarm;
void Start();
void Stop();
}
Forget about scheduler for now. Return and implement your second class, which will display some warnings. Let's go test first (with Moq):
[Test]
public void ShouldStopDisplayingWarningsWhenTimeIsOut()
{
Mock<IDisplay> display = new Mock<IDisplay>();
Mock<IScheduler> scheduler = new Mock<IScheduler>();
Foo foo = new Foo("Bar", scheduler.Object, display.Object);
scheduler.Raise(s => s.Alarm += null, new SchedulerEventArgs(0));
display.Verify(d => d.Execute("Bar", WarningState.Ending, null));
scheduler.Verify(s => s.Stop());
}
Write implementation:
public class Foo
{
private readonly IScheduler _scheduler;
private readonly IDisplay _display;
private readonly string _name;
public Foo(string name, IScheduler scheduler, IDisplay display)
{
_name = name;
_display = display;
_scheduler = scheduler;
_scheduler.Alarm += Scheduler_Alarm;
_scheduler.Start();
}
private void Scheduler_Alarm(object sender, SchedulerEventArgs e)
{
_display.Execute(_name, WarningState.Ending, null);
_scheduler.Stop();
}
}
Test passes. Write another one:
[Test]
public void ShouldNotStopDisplayingWarningsWhenTimeRemains()
{
Mock<IDisplay> display = new Mock<IDisplay>(MockBehavior.Strict);
Mock<IScheduler> scheduler = new Mock<IScheduler>(MockBehavior.Strict);
scheduler.Setup(s => s.Start());
Foo foo = new Foo("Bar", scheduler.Object, display.Object);
scheduler.Raise(s => s.Alarm += null, new SchedulerEventArgs(1));
}
Test failed. Ah, you need condition for remaining time:
private void Scheduler_Alarm(object sender, SchedulerEventArgs e)
{
if (e.RemainingTime > 0)
return;
_display.Execute(_name, WarningState.Ending, null);
_scheduler.Stop();
}
You can continue writing tests for your class, which responsible for handling scheduler alerts and executing some warnings on display. When you finish, you can write implementation for your IScheduler
interface. It does not matter how you will implement scheduling - via System.Windows.Forms.Timer or via System.ThreadingTimer, or some other way.
Solution 2:
Is it worth testing the method? (since it's private)
Your purpose is to decide whether your code works or not. Even it's a private method, it should generate an output that can be reached by the public interface. You should design your class in a way that the user can know if it's working or not.
Also when you are unit testing, the callback assigned to Elapsed event of the timer is reachable if you can mock the timer.
How can I test it? (I know I can have my test class inheriting the class I want to test...)
You can use an adapter class here. First you have to define an abstraction since Timer class doesn't offer one.
public interface ITimer
{
void Start();
void Stop();
double Interval { get; set; }
event ElapsedEventHandler Elapsed;
//and other members you need
}
Then you can implement this interface in an adapter class, just inheriting from the Timer class.
public class TimerAdaper : Timer, ITimer { }
You should inject your abstraction in the constructor(or as a property) so you can mock it in your tests.
public class MyClass
{
private readonly ITimer _timer;
public MyClass(ITimer timer)
{
_timer = timer
}
}
Should I be mocking my timer? Because if I have to test a class that uses an internal timer, my tests may take a lot of time to complete, right?
Of course you should be mocking your timer. Your unit tests cannot depend on system time. You should raise events by mocking and see how your code behaves.