EventWaiter – testowanie zdarzeń

Dzisiaj chciałbym zaprezentować klasę EventWaiter. Znalazłem ją w repozytorium Roslyn na GitHub i w niektórych testach okazała się przydatna. Załóżmy, że mamy klasę eksponującą jakieś zdarzenie:

    class Sut
    {
        public event EventHandler Click;

        public void RaiseClickEvent()
        {
            Click?.Invoke(this,EventArgs.Empty);
        }
    }

Test zdarzenia mógłby wyglądać następująco:

        [Test]
        [Timeout(2000)]
        public void Test1()
        {
            // arrange
            var manualResetEvent = new ManualResetEvent(false);
            var sut = new Sut();

            sut.Click += (s, e) => { manualResetEvent.Set(); };

            // act
            sut.RaiseClickEvent();

            // assert
            manualResetEvent.WaitOne();
            Assert.Pass();
        }

Źródło klasy EventWaiter można znaleźć np. tutaj:
https://github.com/dotnet/roslyn/blob/master/src/Test/Utilities/Shared/FX/EventWaiter.cs

Kod:

  public sealed class EventWaiter : IDisposable
    {
        private readonly ManualResetEvent _eventSignal = new ManualResetEvent(false);
        private Exception _capturedException;

        ///
<summary>
        /// Returns the lambda given with method calls to this class inserted of the form:
        /// 
        /// try
        ///     execute given lambda.
        ///     
        /// catch
        ///     capture exception.
        ///     
        /// finally
        ///     signal async operation has completed.
        /// </summary>

        /// <typeparam name="T">Type of delegate to return.</typeparam>
        /// <param name="input">lambda or delegate expression.</param>
        /// <returns>The lambda given with method calls to this class inserted.</returns>
        public EventHandler<T> Wrap<T>(EventHandler<T> input)
        {
            return (sender, args) =>
            {
                try
                {
                    input(sender, args);
                }
                catch (Exception ex)
                {
                    _capturedException = ex;
                }
                finally
                {
                    _eventSignal.Set();
                }
            };
        }

        ///
<summary>
        /// Use this method to block the test until the operation enclosed in the Wrap method completes
        /// </summary>

        /// <param name="timeout"></param>
        /// <returns></returns>
        public bool WaitForEventToFire(TimeSpan timeout)
        {
            var result = _eventSignal.WaitOne(timeout);
            _eventSignal.Reset();
            return result;
        }

        ///
<summary>
        /// Use this method to block the test until the operation enclosed in the Wrap method completes
        /// </summary>

        /// <param name="timeout"></param>
        /// <returns></returns>
        public void WaitForEventToFire()
        {
            _eventSignal.WaitOne();
            _eventSignal.Reset();
            return;
        }

        ///
<summary>
        /// IDisposable Implementation.  Note that this is where we throw our captured exceptions.
        /// </summary>

        public void Dispose()
        {
            _eventSignal.Dispose();
            if (_capturedException != null)
            {
                throw _capturedException;
            }
        }
    }

Dzięki temu wrapperowi, nasz kod może wyglądać teraz tak:


        [Test]
        [Timeout(2000)]
        public void Test1a()
        {
            // arrange
            var waiter=new EventWaiter();            
            var sut = new Sut();
            sut.Click += waiter.Wrap<EventArgs>((sender, args) => { Assert.Pass()});

            // act
            sut.RaiseClickEvent();

            // assert
            waiter.WaitForEventToFire();
        }

Powyższy przykład jest prosty. W praktyce musimy wciąć pod uwagę różne scenariusze – np. gdy wyjątek jest wyrzucany albo musimy sprawdzić konkretne parametry zdarzenia. W takich sytuacjach, powyższy wrapper jest przydatny.

Leave a Reply

Your email address will not be published.