NMock issue testing against WPF and Dispatcher

僤鯓⒐⒋嵵緔 提交于 2019-12-12 18:35:54

问题


Here's one for the threading junkies out there. I've got this method:

    public void RefreshMelts()
    {
        MeltsAvailable.Clear();

        ThreadPool.QueueUserWorkItem(delegate
        {
            Dispatcher.BeginInvoke((ThreadStart)delegate
            {
                eventAggregator.GetEvent<BusyEvent>().Publish(true);
                eventAggregator.GetEvent<StatusMessageEvent>().Publish(
                    new StatusMessage("Loading melts...", MessageSeverity.Low));
            });

            try
            {
                IList<MeltDto> meltDtos = meltingAppService.GetActiveMelts();

                Dispatcher.Invoke((ThreadStart)delegate
                {
                    foreach (MeltDto availableMelt in meltDtos)
                    {
                        MeltsAvailable.Add(availableMelt);
                    }
                    OnPropertyChanged("MeltsAvailable");

                    eventAggregator.GetEvent<BusyEvent>().Publish(false);
                    eventAggregator.GetEvent<StatusMessageEvent>().Publish(
                        new StatusMessage("Melts loaded", MessageSeverity.Low));
                });
            }
            catch (ApplicationException ex)
            {
                log.Error("An error occurred in MeltsViewModel when attempting to load melts", ex);

                Dispatcher.Invoke((ThreadStart)delegate
                {
                    MeltsAvailable.Clear();

                    eventAggregator.GetEvent<StatusMessageEvent>().Publish(
                        new StatusMessage("Melt data could not be loaded because an error occurred; " +
                            "see the application log for detail",
                            MessageSeverity.High));
                    eventAggregator.GetEvent<BusyEvent>().Publish(false);
                });
            }

        });

    }

This is defined in a WPF user control. MeltsAvailable is an ObservableCollection of MeltDtos. This code works beautifully when running in the application itself.

The trouble is that I would like to create a unit test, using NMock, to verify the results of this method - specifically, that once it's called, the MeltsAvailable property has some items. Here's the test method:

    [TestMethod]
    public void GetAvailableMeltsTest()
    {
        MeltDto mockMelt1 = new MeltDto();
        MeltDto mockMelt2 = new MeltDto();

        mockMelt1.MeltIdentifier = "TST0001";
        mockMelt2.MeltIdentifier = "TST0002";

        IList<MeltDto> availableMelts = new List<MeltDto>();
        availableMelts.Add(mockMelt1);
        availableMelts.Add(mockMelt2);

        Expect.Exactly(1).On(service).Method("GetActiveMelts").Will(Return.Value(availableMelts));


        MeltsViewModel vm = new MeltsViewModel(aggregator, logger, service, configManagerFactory); // All of these are mock objects

        vm.RefreshMelts();
        Thread.Sleep(millisecondDelayForEventPublish * 100);

        mockery.VerifyAllExpectationsHaveBeenMet();

        Assert.AreEqual(vm.MeltsAvailable.Count, 2);
        Assert.AreEqual(vm.MeltsAvailable[0].MeltIdentifier, "TST0001");
        Assert.AreEqual(vm.MeltsAvailable[1].MeltIdentifier, "TST0002");

    }

The test consistently fails on the first Assert.AreEqual. vm.MeltsAvailable is empty at that point.

If I strip out all the threading and leave it just as:

    public void RefreshMelts()
    {
        MeltsAvailable.Clear();
        IList<MeltDto> meltDtos = meltingAppService.GetActiveMelts();
        foreach (MeltDto availableMelt in meltDtos)
        {
            MeltsAvailable.Add(availableMelt);
        }
        OnPropertyChanged("MeltsAvailable");
    }

The test passes.

So, obviously, there's something it doesn't like about the threads - but even turning on Debug->Exceptions->CLR Exceptions->Thrown, and turning off Just My Code, I get no exceptions at all in RefreshMelts.

The strangest part is that the Dispatcher.Invoke call where I load the MeltDto objects into the MeltsAvailable collection never seems to be called. I can blanket the whole section with breakpoints, and they never get hit. Boosting the Thread.Sleep time in my test to even as high as ten seconds changes nothing.

Why? Why is that section not executing, why can't I step into it or break into it, why am I not getting exceptions, why does it work fine in execution but not in a test?

Thanks much, Steve


回答1:


The Dispatcher is a message loop that is tied to the executing thread. It processes the items in its queue when the main thread is idle. In a unit test, that never happens. The thread is busy and then it exits when the test is completed.

If you're using Visual Studio to run your tests, you can turn on code coverage highlighting and you'll see that code inside Dispatcher.Invoke() is never called (it will be displayed in red).

A DispatcherFrame can be used to trigger the Dispatcher to process queued messages. Add the following helper class to your unit test project:

public static class DispatcherHelper 
{ 
    public static void DoEvents() 
    {
        DispatcherFrame frame = new DispatcherFrame(); 
        Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame);
        Dispatcher.PushFrame(frame); 
    } 

    private static object ExitFrame(object frame) 
    { 
        ((DispatcherFrame)frame).Continue = false; 
        return null; 
    } 
}

At the end of your test (prior to the assertions) call DispatcherHelper.DoEvents(). This will trigger the Dispatcher to process outstanding events, such as the ones that add items to the view model's observable collection. You can then inspect the view model's properties to verify that they were set correctly.



来源:https://stackoverflow.com/questions/4620250/nmock-issue-testing-against-wpf-and-dispatcher

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!