Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I run the event handler assigned to a mock?

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.

like image 307
Ufuk Hacıoğulları Avatar asked Jan 20 '12 11:01

Ufuk Hacıoğulları


3 Answers

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.

like image 124
JoanComasFdz Avatar answered Oct 23 '22 16:10

JoanComasFdz


The Moq QuickStart guide has a section on events. I think you'd use

mock.Raise(m => m.Elapsed += null, new ElapsedEventArgs(...));
like image 5
Jon Skeet Avatar answered Oct 23 '22 16:10

Jon Skeet


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));
like image 4
Yoyo Avatar answered Oct 23 '22 16:10

Yoyo