In Composite WPF (Prism), how should I unit test m

2020-07-24 04:18发布

问题:

I'm building a basic Composite WPF Shell with one Module. I would like to unit test my module. Apparently Composite WPF modularizes my code in such a way that it should be easy to unit test.

Below is the code I would like to Unit Test. It resides in my Module's Controller. Note the use of the standard Composite WPF entities like Regions, Presenters, Models etc.

    public void ShowPlantTreeView()
    {
        IRegion navRegion = this.regionManager.Regions[RegionNames.NavigationRegion];
        IPlantTreeView view = navRegion.GetView(typeof(IPlantTreeView).Name) as IPlantTreeView;
        if (view == null)
        {
            view = this.container.Resolve<IPlantTreePresentationModel>().View;
            navRegion.Add(view, typeof(IPlantTreeView).Name);
        }

        view.Model.LastRefreshDateTime = DateTime.Now;
        navRegion.Activate(view);
    }

This is only seven lines of code I want to unit test. Not too bad. The trouble is that it depends on a number of external components - RegionManager, View, PresentationModel etc.

In order to test this independently I Mock the external components. These are passed into my Controller through Constructor Injection using the Unity Container. In order to configure this and do a simple test, my unit test looks as follows...

(Look at the length of this method! Surely there must be a better way to test? Does composite WPF really make my life easier? And I have to do this for every test?!)

    [TestMethod]
    public void TestShowPlantTree()
    {
        //Setup Mocks.
        var plantTreePresentationModel = new Mock<IPlantTreePresentationModel>();
        var plantTreeViewMock = new Mock<IPlantTreeView>();
        var navRegionMock = new Mock<IRegion>();
        var plantTreeModuleMock = new Mock<IPlantTreeModule>();
        var regionManagerMock = new Mock<IRegionManager>();
        var eventAggregatorMock = new Mock<IEventAggregator>();
        var shellControllerMock = new Mock<IShellController>();
        var plantTreeNodeSelectedEventMock = new Mock<PlantTreeNodeSelectedEvent>();

        plantTreeViewMock.Setup(v => v.Model).Returns(plantTreePresentationModel.Object);
        container.RegisterInstance<IPlantTreePresentationModel>(plantTreePresentationModel.Object);
        regionManagerMock.Setup(o => o.Regions[RegionNames.NavigationRegion]).Returns(navRegionMock.Object);
        navRegionMock.Setup(r => r.GetView(typeof(IPlantTreeView).Name)).Returns(plantTreeViewMock.Object);
        navRegionMock.Setup(r => r.Activate(plantTreeViewMock.Object));
        plantTreePresentationModel.SetupSet(m => m.LastRefreshDateTime);
        eventAggregatorMock.Setup(a => a.GetEvent<PlantTreeNodeSelectedEvent>()).Returns(plantTreeNodeSelectedEventMock.Object);


        //Setup container.
        container.RegisterType<IPlantTreeController, PlantTreeController>();
        container.RegisterInstance<IPlantTreePresentationModel>(plantTreePresentationModel.Object);
        container.RegisterInstance<IPlantTreeView>(plantTreeViewMock.Object);
        container.RegisterInstance<IRegion>(navRegionMock.Object);
        container.RegisterInstance<IPlantTreeModule>(plantTreeModuleMock.Object);
        container.RegisterInstance<IRegionManager>(regionManagerMock.Object);
        container.RegisterInstance<IEventAggregator>(eventAggregatorMock.Object);
        container.RegisterInstance<IShellController>(shellControllerMock.Object);
        container.RegisterInstance<PlantTreeNodeSelectedEvent>(plantTreeNodeSelectedEventMock.Object);


        //Initialize controller to be tested.
        IPlantTreeController controllerToTest = container.Resolve<IPlantTreeController>();

        controllerToTest.ShowPlantTreeView();


        //Test if controller interacted with the mocks as expected.
        plantTreePresentationModel.VerifyAll();
        regionManagerMock.VerifyAll();
        navRegionMock.VerifyAll();
    }

Is there a better way to test my class? Any advice would be appreciated.

回答1:

I've run across this myself with classes with a lot of dependencies. There's really not much of a way around it.

Does your method really depend on all of these classes? I only see two or three dependencies being utilized here (IRegionManager, IPlantTreePresentationModel). Those should be the only ones you have to mock to test your method. You might test this using a set of dependencies that is appropriate for your test, rather than for any test of this object.

The other thing you might consider is how many of these dependencies you can factor into your test's startup code (method decorated with [TestInitialize]). Some common dependencies and your container could live in a scope for your entire test suite, especially if they don't change per test.

Dependency injection certainly does make your life easier, even if you don't realize it. I find that a lot of people doing "unit testing" are not really doing it right and are doing functional testing due to the fact that they don't have proper isolation from other parts of their app.

Dependency injection almost forces you into a model where you are able to completely isolate the code you actually want to test away from all of the other parts of your app. It might cost a little up front, but the quality of your unit tests and the granularity of the feedback you are getting from them will outweigh those costs, especially when you get later in your app's lifecycle and start refactoring. You'll thank yourself then. Stick with it.