3

I am doing my first steps in MVVM.

I am using Caliburn.Micro as my MVVM framework.

I have the following code:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Globalization;

using Caliburn.Micro;

namespace MY_PROJECT.ViewModels
{   
    [Export(typeof(MainViewModel))]
    public class MainViewModel : Screen
    {     
        private readonly IWindowManager _windowManager = null;

        private readonly IEventAggregator _events;

        private string _title = string.Empty;          

        private WindowState _windowState = WindowState.Normal;

        public string Title
        {
            get
            {
               return _title;
            }
            set
            {
               _title = value;
               NotifyOfPropertyChange(() => Title);
            }
        }

        public WindowState WindowState
        {
            get
            {
                return _windowState;
            }
            set
            {
                _windowState = value;
                NotifyOfPropertyChange(() => WindowState);
            }
        }

        public void ChangeTittle(string title)
        {
             Title = title;
        }

        public void ToggleWindowState()
        {
             WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
        }

        [ImportingConstructor]
        public MainViewModel(IWindowManager windowManager, IEventAggregator events)
        {
            _windowManager = windowManager;
            _events = events;
            _events.Subscribe(this);
        }
  }

I now want to write some simple unit tests to test my viewmodel.

Any suggestions of how to do that?

Caliburn.Micro's documentation seems to be missing this information.

4

1 回答 1

5

Ok you could just run the entire app through the IoC container and bootstrap the whole thing, this way you get actual concrete implementations for all your interfaces. The downside to that is that you will need everything available (databases, web services etc) and can be more difficult to test an application if you pretty much need it all running (and it may be much slower to execute tests since actual work is being done). If you can easily go this route, then by all means use it.

Alternatively you could mock the behaviour/state of the objects using mocks or stubs by using a mocking framework such as Moq

With Moq you setup your test environment so that your interfaces and classes are represented by Mock<T> (mock objects) which you specify the behaviour for. Then you test against that behaviour in your ViewModels

Here's an example set of tests using Moq and NUnit for your MainViewModel in it's current incarnation:

// Decorate with test fixture attribute so NUnit knows it's a test
[TestFixture]
class MainViewModelTests
{           
    // The interfaces/instances you will need to test with - this is your test subject
    MainViewModel _mainVM;

    // You can mock the other interfaces:
    Mock<IWindowManager> _windowManager;
    Mock<IEventAggregator> _eventAggregator; 

    // Setup method will run at the start of each test
    [SetUp]
    public void Setup() 
    {
        // Mock the window manager
        _windowManager = new Mock<IWindowManager>();               

        // Mock the event aggregator
        _windowManager = new Mock<IEventAggregator>(); 

        // Create the main VM injecting the mocked interfaces
        // Mocking interfaces is always good as there is a lot of freedom
        // Use mock.Object to get hold of the object, the mock is just a proxy that decorates the original object
        _mainVM = new MainViewModel(_windowManager.Object, _eventAggregator.Object);
    }

    // Create a test to make sure the VM subscribes to the aggregator (a GOOD test, I forget to do this a LOT and this test gives me a slap in the face)
    [Test]
    public void Test_SubscribedToEventAggregator()
    {
        // Test to make sure subscribe was called on the event aggregator at least once
        _eventAggregator.Verify(x => x.Subscribe(_mainVM));
    }

    // Check that window state toggles ok when it's called
    [Test]
    public void Test_WindowStateTogglesCorrectly()
    {
        // Run the aggregator test at the start of each test (this will run as a 'child' test)
        Test_SubscribedToEventAggregator();

        // Check the default state of the window is Normal
        Assert.True(_mainVM.WindowState == WindowState.Normal);

        // Toggle it
        _mainVM.ToggleWindowState();

        // Check it's maximised
        Assert.True(_mainVM.WindowState == WindowState.Maximised);

        // Check toggle again for normal
        _mainVM.ToggleWindowState();

        Assert.True(_mainVM.WindowState == WindowState.Normal);
    }

    // Test the title changes correctly when the method is called
    [Test]
    public void Test_WindowTitleChanges()
    {
         Test_SubscribedToEventAggregator();

         _mainVM.ChangeTitle("test title");
         Assert.True(_mainVM.Title == "test title");
    }
}

You can see how you can test state and behaviour, I expected a certain VM state when VM methods such as ChangeTitle were called, and I also expected a behaviour (I expected Subscribe(X) to be called on the aggregator at least once at the start of each test).

The method decorated with [SetUp] will be called at the start of each test. There are teardown and other methods (including one to setup the entire test fixture, i.e. it only runs once per fixture)

The key thing here is probably that for CM you don't actually need to mock any behaviour in the event aggregator since CM expects you to implement IHandle<T> for your event aggregator messages. Making these subscriber methods an interface implementation means that you will already have public methods on your object which you can call to simulate event aggregator calls.

So for instance you can use

public class MainViewModel : IHandle<someEventMessageArgument> // (this is your subscriber interface)
{ 
    // (and this is the corresponding method)
    public void Handle(someEventMessageArgument message) 
    { 
        // do something useful maybe change object state or call some methods
    }
}

// Test the method - you don't need to mock any event aggregator behaviour since you have tested that the VM was subscribed to the aggregator. (OK CM might be broken but that's Robs problem :))
[Test]
Test_SomeEventDoesWhatYouAreExpecting()
{
    _mainVM.Handle(someEventMessageArgument);

    // Assert that what was supposed to happen happened...
    Assert.True(SomethingHappened);
}

Check out the Moq quickstart here:

http://code.google.com/p/moq/wiki/QuickStart

于 2013-03-25T10:55:47.747 回答