Reputation: 38468
I am trying to fire the event handler assigned to my timer mock. How can I test this private method here?
public interface ITimer
{
void Start();
double Interval { get; set; }
event ElapsedEventHandler Elapsed;
}
Client class assigns an event handler to this object. I want to test the logic in this class.
_timer.Elapsed += ResetExpiredCounters;
And the assigned method is private
private void ResetExpiredCounters(object sender, ElapsedEventArgs e)
{
// do something
}
I want to have this event handler in my mock and run it somehow. How can I do this?
Update:
I realized I was raising the event before I assigned the event handler. I corrected that but I still get this error:
System.ArgumentException : Object of type 'System.EventArgs' cannot be converted
to type 'System.Timers.ElapsedEventArgs'.
I raise it like this:
_timer.Raise(item => item.Elapsed += null, ElapsedEventArgs.Empty);
or
_timer.Raise(item => item.Elapsed += null, EventArgs.Empty);
Both won't work.
Update:
Here's the thing that worked for me. Note that it's not useful if you are trying to pass info to event handler like Jon pointed out in comments. I am just using it to mock the wrapper for System.Timers.Timer class.
_timer.Raise(item => item.Elapsed += null, new EventArgs() as ElapsedEventArgs);
In the end, this won't help at all if you need to use event arguments since it will be always null. However, it's the only way since ElapsedEventArgs has only an internal constructor.
Upvotes: 17
Views: 10081
Reputation: 1806
The OP points out that they can simply use new EventArgs() as ElapsedEventArgs
which is a nice simple way to get it done in one line, and assuming the object is never examined in the handler it will work - but it will generate a "possible null reference" build warning.
If you're aiming for zero build warnings then the best method for this use case, avoiding the obsolete classes in Yoyo's answer, is something like this:
private ElapsedEventArgs CreateElapsedEventArgs()
{
return (ElapsedEventArgs)RuntimeHelpers.GetUninitializedObject(typeof(ElapsedEventArgs));
}
Upvotes: 1
Reputation: 2827
Could do something like this to wrap your Timer
public class FakeTimer : IMyTimer
{
private event ElapsedEventHandler elaspedHandler;
private bool _enabled;
public void Dispose() => throw new NotImplementedException();
public FakeTimer(ElapsedEventHandler elapsedHandlerWhenTimeFinished, bool startImmediately)
{
this.elaspedHandler = elapsedHandlerWhenTimeFinished;
_enabled = startImmediately;
}
public void Start() => _enabled = true;
public void Stop() => _enabled = false;
public void Reset() => _enabled = true;
internal void TimeElapsed()
{
if (this._enabled)
elaspedHandler.Invoke(this, new EventArgs() as ElapsedEventArgs);
}
}
Upvotes: 0
Reputation: 41
Dealt with this recently, you can construct an ElapsedEventArgs using reflection:
public ElapsedEventArgs CreateElapsedEventArgs(DateTime signalTime)
{
var e = FormatterServices.GetUninitializedObject(typeof(ElapsedEventArgs)) as ElapsedEventArgs;
if (e != null)
{
var fieldInfo = e.GetType().GetField("signalTime", BindingFlags.NonPublic | BindingFlags.Instance);
if (fieldInfo != null)
{
fieldInfo.SetValue(e, signalTime);
}
}
return e;
}
This way you can continue using the original ElapsedEventHandler delegate
var yesterday = DateTime.Now.AddDays(-1);
timer.Raise(item => item.Elapsed += null, CreateElapsedEventArgs(yesterday));
Upvotes: 4
Reputation: 3066
ElapsedEventArgs
has a private constructor and can not be instantiated.
If you use:
timer.Raise(item => item.Elapsed += null, new EventArgs() as ElapsedEventArgs);
Then the handler will recevie a null parameter and lose its SignalTime
property:
private void WhenTimerElapsed(object sender, ElapsedEventArgs e)
{
// e is null.
}
You might want this parameter in some cases.
To solve this and make it more testable, I also created a wrapper for the ElapsedEventArgs
, and made the interface use it:
public class TimeElapsedEventArgs : EventArgs
{
public DateTime SignalTime { get; private set; }
public TimeElapsedEventArgs() : this(DateTime.Now)
{
}
public TimeElapsedEventArgs(DateTime signalTime)
{
this.SignalTime = signalTime;
}
}
public interface IGenericTimer : IDisposable
{
double IntervalInMilliseconds { get; set; }
event EventHandler<TimerElapsedEventArgs> Elapsed;
void StartTimer();
void StopTimer();
}
The implementation will simply fire its own event getting the data from the real timer event:
public class TimerWrapper : IGenericTimer
{
private readonly System.Timers.Timer timer;
public event EventHandler<TimerElapsedEventArgs> Elapsed;
public TimeSpan Interval
{
get
{
return this.timer.Interval;
}
set
{
this.timer.Interval = value;
}
}
public TimerWrapper (TimeSpan interval)
{
this.timer = new System.Timers.Timer(interval.TotalMilliseconds) { Enabled = false };
this.timer.Elapsed += this.WhenTimerElapsed;
}
private void WhenTimerElapsed(object sender, ElapsedEventArgs elapsedEventArgs)
{
var handler = this.Elapsed;
if (handler != null)
{
handler(this, new TimeElapsedEventArgs(elapsedEventArgs.SignalTime));
}
}
public void StartTimer()
{
this.timer.Start();
}
public void StopTimer()
{
this.timer.Stop();
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
this.timer.Elapsed -= this.WhenTimerElapsed;
this.timer.Dispose();
}
this.disposed = true;
}
}
}
Now, you can simplify and improve the mock of this event:
timer.Raise(item => item.Elapsed += null, new TimeElapsedEventArgs());
var yesterday = DateTime.Now.AddDays(-1);
timer.Raise(item => item.Elapsed += null, new TimeElapsedEventArgs(yesterday));
Less code to write, easier to work with and completely decoupled from the framework.
Upvotes: 18
Reputation: 1500485
The Moq QuickStart guide has a section on events. I think you'd use
mock.Raise(m => m.Elapsed += null, new ElapsedEventArgs(...));
Upvotes: 4