如何运行分配给模拟的事件处理程序?

时间:2012-01-20 11:49:54

标签: c# .net event-handling moq

我正在尝试触发分配给我的计时器模拟的事件处理程序。我如何在这里测试这个私有方法?

public interface ITimer
{
    void Start();
    double Interval { get; set; }
    event ElapsedEventHandler Elapsed;
}

客户端类为此对象分配事件处理程序。我想测试这个类中的逻辑。

_timer.Elapsed += ResetExpiredCounters;

指定的方法是私有的

private void ResetExpiredCounters(object sender, ElapsedEventArgs e)
{
    // do something
}

我希望在我的模拟中使用此事件处理程序并以某种方式运行它。我怎么能这样做?

更新

我意识到在分配事件处理程序之前我正在提升事件。我纠正了这个,但我仍然得到这个错误:

System.ArgumentException : Object of type 'System.EventArgs' cannot be converted 
to type 'System.Timers.ElapsedEventArgs'.

我这样举起来:

_timer.Raise(item => item.Elapsed += null, ElapsedEventArgs.Empty);

_timer.Raise(item => item.Elapsed += null, EventArgs.Empty);

两者都行不通。

更新

这对我有用。请注意,如果您尝试将信息传递给事件处理程序(如Jon在评论中指出),则无效。我只是用它来模拟System.Timers.Timer类的包装器。

_timer.Raise(item => item.Elapsed += null, new EventArgs() as ElapsedEventArgs);

最后,如果你需要使用事件参数,这根本不会有用,因为它总是为null。但是,这是ElapsedEventArgs只有一个内部构造函数的唯一方法。

3 个答案:

答案 0 :(得分:11)

ElapsedEventArgs有一个私有构造函数,无法实例化。

如果您使用:

timer.Raise(item => item.Elapsed += null, new EventArgs() as ElapsedEventArgs);

然后处理程序将接收一个null参数并丢失其SignalTime属性:

private void WhenTimerElapsed(object sender, ElapsedEventArgs e)
{
    // e is null.
}

在某些情况下,您可能需要此参数。

为了解决这个问题并使其更易于测试,我还为ElapsedEventArgs创建了一个包装器,并让界面使用它:

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();
}

实现将简单地触发自己的事件来获取真实计时器事件中的数据:

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;
        }
    }
}

现在,您可以简化和改进此事件的模拟:

timer.Raise(item => item.Elapsed += null, new TimeElapsedEventArgs());

var yesterday = DateTime.Now.AddDays(-1);
timer.Raise(item => item.Elapsed += null, new TimeElapsedEventArgs(yesterday));

编写更少的代码,更易于使用并完全与框架分离。

答案 1 :(得分:5)

Moq QuickStart guide有一个关于事件的部分。我想你会用

mock.Raise(m => m.Elapsed += null, new ElapsedEventArgs(...));

答案 2 :(得分:0)

最近解决了这个问题,你可以使用反射构建一个ElapsedEventArgs:

    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;
    }

这样您就可以继续使用原始的ElapsedEventHandler委托

var yesterday = DateTime.Now.AddDays(-1);
timer.Raise(item => item.Elapsed += null, CreateElapsedEventArgs(yesterday));