How do I test Prism event aggregator subscriptions, on the UIThread?

后端 未结 3 821
慢半拍i
慢半拍i 2020-12-31 04:07

I have a class, that subscribes to an event via PRISMs event aggregator.

As it is somewhat hard to mock the event aggregator as noted here, I just instantiate a real

3条回答
  •  情书的邮戳
    2020-12-31 04:48

    You may not like this as it may involve what you feel is an "ugly hack", but my preference IS to use a real EventAggregator rather than mocking everything. While ostensibly an external resource, the EventAggregator runs in memory and so does not require much set-up, clear down, and is not a bottle neck like other external resources such as databases, web-services, etcetera would be and therefore I feel it is appropriate to use in a unit test. On that basis I have used this method to overcome the UI thread issue in NUnit with minimal change or risk to my production code for the sake of the tests.

    Firstly I created an extension method like so:

    public static class ThreadingExtensions
    {
        private static ThreadOption? _uiOverride;
    
        public static ThreadOption UiOverride
        {
            set { _uiOverride = value; }
        }
    
        public static ThreadOption MakeSafe(this ThreadOption option)
        {
            if (option == ThreadOption.UIThread && _uiOverride != null)
                return (ThreadOption) _uiOverride;
    
            return option;
        }
    

    }

    Then, in all my event subscriptions I use the following:

    EventAggregator.GetEvent().Subscribe
    (
        x => // do stuff, 
        ThreadOption.UiThread.MakeSafe()
    );
    

    In production code, this just works seamlessly. For testing purposes, all I have to do is add this in my set-up with a bit of synchronisation code in my test:

    [TestFixture]
    public class ExampleTest
    {
        [SetUp]
        public void SetUp()
        {
            ThreadingExtensions.UiOverride = ThreadOption.Background;
        }
    
        [Test]
        public void EventTest()
        {
            // This doesn't actually test anything useful.  For a real test
            // use something like a view model which subscribes to the event
            // and perform your assertion on it after the event is published.
            string result = null;
            object locker = new object();
            EventAggregator aggregator = new EventAggregator();
    
            // For this example, MyEvent inherits from CompositePresentationEvent
            MyEvent myEvent = aggregator.GetEvent();
    
            // Subscribe to the event in the test to cause the monitor to pulse,
            // releasing the wait when the event actually is raised in the background
            // thread.
            aggregator.Subscribe
            (
                x => 
                {
                    result = x;
                    lock(locker) { Monitor.Pulse(locker); }
                },
                ThreadOption.UIThread.MakeSafe()
            );
    
            // Publish the event for testing
            myEvent.Publish("Testing");
    
            // Cause the monitor to wait for a pulse, but time-out after
            // 1000 millisconds.
            lock(locker) { Monitor.Wait(locker, 1000); }
    
            // Once pulsed (or timed-out) perform your assertions in the real world
            // your assertions would be against the object your are testing is
            // subscribed.
            Assert.That(result, Is.EqualTo("Testing"));
        }
    }
    

    To make the waiting and pulsing more succinct I have also added the following extension methods to ThreadingExtensions:

        public static void Wait(this object locker, int millisecondTimeout)
        {
            lock (locker)
            {
                Monitor.Wait(locker);
            }
        }
    
        public static void Pulse(this object locker)
        {
            lock (locker)
            {
                Monitor.Pulse(locker);
            }
        }
    

    Then I can do:

    // 
    aggregator.Subscribe(x => locker.Pulse(), ThreadOption.UIThread.MakeSafe());
    
    myEvent.Publish("Testing");
    
    locker.Wait(1000);
    // 
    

    Again, if your sensibilities mean you want to use mocks, go for it. If you'd rather use the real thing, this works.

提交回复
热议问题