Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to create a Unit Testable Solution in Silverlight

0.00/5 (No votes)
9 May 2010 1  
This article is about the best practices to write a Silverlight application which is highly unit testable.

Introduction

A unit test verifies that a function or set of functions "honors its contract" - in other words, that the function(s) under test meet the requirements. Unit tests inspect both black boxes and white boxes.

This article is not about what is unit testing or how to write unit test cases. There are many articles available on the internet. Here is a very good link which you can follow to get the information on Unit testing.

It is very common among developers to write code in XAML code behind file to achieve the feature but it reduces the testability of the application. This article describes how to write a code which is highly testable.

Background

UnitTestDemoApp

I got the idea of this application from Stock Trader Reference application of PRISM framework. The application has two grids. The first grid lists all the available stocks. The user can select one stock and add it to watch list. The second grid contains the stocks which user wants to watch. The user can select a stock from the watch list and click on Remove to remove the stock from watch list. The user can enter the text in the Search textbox and click on Search button to search the stocks.

Here we wouldn’t concentrate on the application and its UI aspect. We would only concentrate on unit testing of the application.

Using the Code

The attached zip file contains two Visual Studio solutions.

  1. UnitTestDemoApp\UnitTestDemo\UnitTestDemo.sln: It is the ideal solution for testing. Almost zero code in XAML code behind.
  2. UnitTestDemoStart\UnitTestDemo\UnitTestDemo.sln: It is a very bad solution for testing. It has almost all the code in code behind. Unit testing is not possible for this solution.

Both the solutions contain the following projects:

  1. UnitTestDemo: Silverlight Application
  2. UnitTestDemo.Web: Web application to host Silverlight application
  3. UnitTestDemoTests: Test project for Silverlight Application

Here are the different classes used in the solution:

  1. PositionSummaryItem: Contains the details of the stock.
  2. AccountPositionService: Service class which retrieves the stock details from the XML file.
  3. CurrencyConverter: Converter to convert show the value in Dollar format. Silverlight 4.0 has support for this. We can specify the format in Silverlight 4.0.
  4. DecimalToColorConverter: Converter to show the value with red or green background color if the value is negative or positive.
  5. PercentConverter: Converter used to show the value in percentage.

Using the Code

Non Testable Application

Here is the code behind code of the XAML class. If you check this code, you would see that we are calling the service directly from the view and changing the source of the grid, filtering the records, enabling/disabling the buttons. Since all the methods are private, we cannot write the unit test cases.

public partial class StockDetailsVeiw : UserControl
    {
        private string searchString = string.Empty;
        private List<positionsummaryitem> stocks = new List<positionsummaryitem>();
        private List<positionsummaryitem> currentStocks = new List<positionsummaryitem>();
        private ObservableCollection<positionsummaryitem> watchList = 
			new ObservableCollection<positionsummaryitem>();
        
        public StockDetailsVeiw()
        {
            InitializeComponent();
            this.btnAddStockToWatchlist.Click += 
		new RoutedEventHandler(btnAddStockToWatchlist_Click);
            this.btnRemoveStockFromWattchList.Click += 
		new RoutedEventHandler(btnRemoveStockFromWattchList_Click);
            this.btnSearch.Click += new RoutedEventHandler(btnSearch_Click);
            this.PositionsGrid.SelectionChanged += 
		new SelectionChangedEventHandler(PositionsGrid_SelectionChanged);
            this.watchListGrid.SelectionChanged += 
		new SelectionChangedEventHandler(watchListGrid_SelectionChanged);
            this.txtSearch.TextChanged += 
		new TextChangedEventHandler(txtSearch_TextChanged);
        }

        private void txtSearch_TextChanged(object sender, TextChangedEventArgs e)
        {
            this.btnSearch.IsEnabled = 
		this.txtSearch.Text.Trim().Length == 0 ? false : true;
        }

        private void watchListGrid_SelectionChanged
		(object sender, SelectionChangedEventArgs e)
        {
            this.btnRemoveStockFromWattchList.IsEnabled = 
		this.watchListGrid.SelectedItem == null ? false : true;
        }

        private void PositionsGrid_SelectionChanged
		(object sender, SelectionChangedEventArgs e)
        {
            this.btnAddStockToWatchlist.IsEnabled = 
		this.PositionsGrid.SelectedItem == null ? false : true;
        }

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            AccountPositionService service = new AccountPositionService();
            this.stocks = service.GetAccountPositions();
            this.currentStocks = this.stocks;
            this.PositionsGrid.ItemsSource = this.currentStocks;
            this.watchListGrid.ItemsSource = this.watchList;
            this.btnSearch.IsEnabled = false;
            this.btnAddStockToWatchlist.IsEnabled = false;
            this.btnRemoveStockFromWattchList.IsEnabled = false;
        }

        private void btnSearch_Click(object sender, RoutedEventArgs e)
        {
            string stringSearch = this.txtSearch.Text.Trim();
            if (!string.IsNullOrEmpty(stringSearch))
            {
                this.currentStocks = (from stock in this.stocks
                                      where stock.TickerSymbol.ToLower().Contains
					(stringSearch.ToLower())
                                      select stock).ToList();
                this.PositionsGrid.ItemsSource = this.currentStocks;
            }
        }

        private void btnRemoveStockFromWattchList_Click(object sender, RoutedEventArgs e)
        {
            if (this.watchListGrid.SelectedItem != null && this.watchList != null &&
                this.watchList.Contains
		((PositionSummaryItem)this.watchListGrid.SelectedItem))
            {
                this.watchList.Remove((PositionSummaryItem) 
				this.watchListGrid.SelectedItem);
            }
        }

        private void btnAddStockToWatchlist_Click(object sender, RoutedEventArgs e)
        {
            if (this.PositionsGrid.SelectedItem != null && this.watchList != null &&
                !this.watchList.Contains((PositionSummaryItem)
				this.PositionsGrid.SelectedItem))
            {
                this.watchList.Add((PositionSummaryItem)this.PositionsGrid.SelectedItem);
            }
        }  

Testable Code

The basis of writing the testable code is to follow a particular pattern. We can select one of the patterns out of MVVM, MVM, and MVC depending on the project and the requirement. MVVM is mostly used for Silverlight application. I followed the MVVM pattern in this application but we can follow any other pattern too.

You can get the definition of different patterns from the following URLs:

Following is the code behind XAML class:

public partial class StockDetailsVeiw : UserControl, IStockDetailsView
    {
        public StockDetailsVeiw()
        {
            InitializeComponent();
        }

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            StockDetailsPresentationModel model = 
		new StockDetailsPresentationModel(this, new AccountPositionService());
            //this.btnSearch.Click += new RoutedEventHandler(btnSearch_Click);
        }

        void btnSearch_Click(object sender, RoutedEventArgs e)
        {
            throw new NotImplementedException();
        }

        #region IStockDetailsView Members

        public IStockDetailsPresentationModel Model
        {
            get
            {
                return this.DataContext as IStockDetailsPresentationModel;
            }
            set
            {
                this.DataContext = value;
            }
        }

        #endregion
    }

You can see that there is almost no code in code behind except the Model property and initialization of the view model for the view. The application is using the binding to get the data from view model. I separated the business logic from the view and wrote it in view model which we can test separately. We can use the same view model for a UI which is completely different, but with the same functionality. We don’t have to make any change in the ViewModel.

Following is the code of View Model:

public class StockDetailsPresentationModel : 
	IStockDetailsPresentationModel, INotifyPropertyChanged
{
    Dictionary<string, PositionSummaryItem> test = 
	new Dictionary<string, PositionSummaryItem>();
    private string searchString = string.Empty;
    private PositionSummaryItem selectedStock;
    private PositionSummaryItem watchListSelectedStock;
    private List<PositionSummaryItem> stocks = new List<PositionSummaryItem>();
    private List<PositionSummaryItem> currentStocks = new List<PositionSummaryItem>();
    private ObservableCollection<PositionSummaryItem> watchList = 
			new ObservableCollection<PositionSummaryItem>();
    private DelegateCommand<string> submitCommand;
    private DelegateCommand<object> addStockToWatchList;
    private DelegateCommand<object> removeStockFromWatchList;
    public event PropertyChangedEventHandler PropertyChanged;
    private IAccountPositionService accountService;
    public StockDetailsPresentationModel(IStockDetailsView view, 
			IAccountPositionService accountService)
    {
        View = view;
        View.Model = this;
        this.accountService = accountService;
        this.AddStockToWatchList = new DelegateCommand<object>
	(this.AddStockToWatchListExecute, this.AddStockToWatchListCanExecute);
        this.RemoveStockFromWatchList = new DelegateCommand<object>
	(this.RemoveStockToWatchListExecute, this.RemoveStockToWatchListCanExecute);
        this.stocks = this.accountService.GetAccountPositions();
        this.CurrentStocks = this.stocks;
        this.SubmitCommand = new DelegateCommand<string>
		(SubmitCommandExecuted, SubmitCommandCanExecute);
    }

    public Dictionary<string, PositionSummaryItem> Dictionary
    {
        get
        {
            return this.test;
        }
        set
        {
            this.test = value;
            this.InvokePropertyChanged("Dictionary");
        }
    }

    public IStockDetailsView View { get; set; }
    public List<PositionSummaryItem> CurrentStocks
    {
        get
        {
            return currentStocks;
        }
        set
        {
            this.currentStocks = value;
            this.InvokePropertyChanged("CurrentStocks");
        }
    }

    public string SearchString
    {
        get
        {
            return searchString;
        }
        set
        {
            if (value != searchString)
            {
                searchString = value;
                InvokePropertyChanged("SearchString");
                this.submitCommand.RaiseCanExecuteChanged();
            }
        }
    }    
    public ICommand SubmitCommand 
    { 
        get 
        { 
            return submitCommand; 
        }
        set
        {
            this.submitCommand = (DelegateCommand<string>)value;
            this.InvokePropertyChanged("SubmitCommand");
        }
    }

    public ICommand RemoveStockFromWatchList
    {
        get
        {
            return removeStockFromWatchList;
        }
        set
        {
            this.removeStockFromWatchList = (DelegateCommand<object>)value;
            this.InvokePropertyChanged("RemoveStockFromWatchList");
        }
    }

    public ICommand AddStockToWatchList
    {
        get
        {
            return addStockToWatchList;
        }
        set
        {
            this.addStockToWatchList = (DelegateCommand<object>)value;
            this.InvokePropertyChanged("AddStockToWatchList");
        }
    }

    public PositionSummaryItem SelectedStock
    {
        get
        {
            return this.selectedStock;
        }
        set
        {
            this.selectedStock = value;
            this.InvokePropertyChanged("SelectedStock");
            this.addStockToWatchList.RaiseCanExecuteChanged();
        }
    }

    public PositionSummaryItem WatchListSelectedStock
    {
        get
        {
            return this.watchListSelectedStock;
        }
        set
        {
            this.watchListSelectedStock = value;
            this.InvokePropertyChanged("WatchListSelectedStock");
            this.removeStockFromWatchList.RaiseCanExecuteChanged();
        }
    }

    public ObservableCollection<PositionSummaryItem> WatchList
    {
        get
        {
            return this.watchList;
        }
        set
        {
            this.watchList = value;
            this.InvokePropertyChanged("WatchList");
        }
    }

    private void InvokePropertyChanged(string property)
    {
        PropertyChangedEventHandler Handler = PropertyChanged;
        if (Handler != null)
        {
            Handler(this, new PropertyChangedEventArgs(property));
        }
    }

    private void SubmitCommandExecuted(string parameter)
    {
        this.CurrentStocks = (from item in this.stocks
        where item.TickerSymbol.ToLower().Contains(parameter.ToLower())
        select item).ToList();
    }
    private void AddStockToWatchListExecute(object stock)
    {
        if (this.SelectedStock != null && this.WatchList != null && 
		!this.WatchList.Contains(this.SelectedStock))
        {
            this.WatchList.Add(this.SelectedStock);
        }
    }
    private void RemoveStockToWatchListExecute(object stock)
    {
        if (this.WatchListSelectedStock != null && this.WatchList != null && 
		this.WatchList.Contains(this.WatchListSelectedStock))
        {
            this.WatchList.Remove(this.WatchListSelectedStock);
        }
    }

    private bool AddStockToWatchListCanExecute(object parameter)
    {
        if (this.SelectedStock == null)
        {
            return false;
        }
        else
        {
            return true;
        }
    }

    private bool RemoveStockToWatchListCanExecute(object parameter)
    {
        if (this.WatchListSelectedStock == null)
        {
            return false;
        }
        else
        {
            return true;
        }
    }

    private bool SubmitCommandCanExecute(string parameter)
    {
        if (this.SearchString == null || this.searchString == string.Empty)
        {
            return false;
        }
        else
        {
            return true;
        }
    }
}

You can see the commands for different buttons which are triggered when user clicks on a button. We can enable/disable the button even without using any UI element (not even button which we want to enable/disable). Silverlight 3.0 doesn’t have support for command. I wrote a class for this. Silverlight 4.0 has support for commands.

One more thing you must have observed is that I am using interfaces everywhere. I am exchanging the object with the help of interfaces. This helps in writing the test cases. The base of unit testing is to test a class at a time. If we are testing a particular class, then all the other classes should be mocked. Interfaces help in creating mock classes.

If you check the UserControl_Loaded method of StockDetailsVeiw class, we are passing the view object while initializing the StockDetailsPresentationModel object but StockDetailsPresentationModel is accepting the object of StockDetailsView as IStockDetailsView. We are doing this so that we can easily mock view object while testing the model.

If you are developing a bigger application and you want to automate this object initialization by using interfaces, then you can use Unity Application Block. For more details on unity application block, refer to the following link:

[TestClass]
public class StockDetailsPresentationModelFixture
{
    private MockAccountPositionService accountPositionService;
    private MockStocksDetailsView stocksDetailsView;

    [TestInitialize]
    public void Setup()
    {
        stocksDetailsView = new MockStocksDetailsView();
        accountPositionService = new MockAccountPositionService();
    }

    [TestMethod]
    public void ShouldRaiseOnPropertyChangedEventWhenDictionaryUpdated()
    {
        bool propertyChangedRaised = false;
        string propertyChangedEventArgsArgument = null;
        var presentationModel = this.CreatePresentationModel();
        presentationModel.PropertyChanged += (sender, e) =>
        {
            propertyChangedRaised = true;
            propertyChangedEventArgsArgument = e.PropertyName;
        };
        Assert.IsFalse(propertyChangedRaised);
        Assert.IsNull(propertyChangedEventArgsArgument);
        presentationModel.Dictionary = new Dictionary<string, PositionSummaryItem>();
        Assert.IsTrue(propertyChangedRaised);
        Assert.AreEqual("Dictionary", propertyChangedEventArgsArgument);
    }

    [TestMethod]
    public void ShouldRaiseOnPropertyChangedEventWhenCurrentStocksUpdated()
    {
        bool propertyChangedRaised = false;
        string propertyChangedEventArgsArgument = null;
        var presentationModel = this.CreatePresentationModel();
        presentationModel.PropertyChanged += (sender, e) =>
        {
            propertyChangedRaised = true;
            propertyChangedEventArgsArgument = e.PropertyName;
        };
        Assert.IsFalse(propertyChangedRaised);
        Assert.IsNull(propertyChangedEventArgsArgument);
        List<PositionSummaryItem> list = new List<PositionSummaryItem>();
        list.Add(new PositionSummaryItem());
        presentationModel.CurrentStocks = list;
        Assert.IsTrue(propertyChangedRaised);
        Assert.AreEqual("CurrentStocks", propertyChangedEventArgsArgument);
    }

    [TestMethod]
    public void ShouldRaiseOnPropertyChangedEventWhenSearchStringUpdated()
    {
        bool propertyChangedRaised = false;
        string propertyChangedEventArgsArgument = null;
        var presentationModel = this.CreatePresentationModel();
        presentationModel.PropertyChanged += (sender, e) =>
        {
            propertyChangedRaised = true;
            propertyChangedEventArgsArgument = e.PropertyName;
        };
        Assert.IsFalse(propertyChangedRaised);
        Assert.IsNull(propertyChangedEventArgsArgument);
        presentationModel.SearchString = "TestSearchString";
        Assert.IsTrue(propertyChangedRaised);
        Assert.AreEqual("SearchString", propertyChangedEventArgsArgument);
    }

    [TestMethod]
    public void ShouldRaiseOnPropertyChangedEventWhenSubmitCommandUpdated()
    {
        bool propertyChangedRaised = false;
        string propertyChangedEventArgsArgument = null;
        var presentationModel = this.CreatePresentationModel();
        presentationModel.PropertyChanged += (sender, e) =>
        {
            propertyChangedRaised = true;
            propertyChangedEventArgsArgument = e.PropertyName;
        };
        Assert.IsFalse(propertyChangedRaised);
        Assert.IsNull(propertyChangedEventArgsArgument);
        presentationModel.SubmitCommand = new DelegateCommand<string>(this.TempMethod);
        Assert.IsTrue(propertyChangedRaised);
        Assert.AreEqual("SubmitCommand", propertyChangedEventArgsArgument);
    }

    [TestMethod]
    public void ShouldRaiseOnPropertyChangedEventWhenAddStockToWatchListUpdated()
    {
        bool propertyChangedRaised = false;
        string propertyChangedEventArgsArgument = null;
        var presentationModel = this.CreatePresentationModel();
        presentationModel.PropertyChanged += (sender, e) =>
        {
            propertyChangedRaised = true;
            propertyChangedEventArgsArgument = e.PropertyName;
        };
        Assert.IsFalse(propertyChangedRaised);
        Assert.IsNull(propertyChangedEventArgsArgument);
        presentationModel.AddStockToWatchList = 
		new DelegateCommand<object>(this.TempMethod); 
        Assert.IsTrue(propertyChangedRaised);
        Assert.AreEqual("AddStockToWatchList", propertyChangedEventArgsArgument);
    }

    [TestMethod]
    public void ShouldRaiseOnPropertyChangedEventWhenSelectedStockUpdated()
    {
        bool propertyChangedRaised = false;
        string propertyChangedEventArgsArgument = null;
        var presentationModel = this.CreatePresentationModel();
        presentationModel.PropertyChanged += (sender, e) =>
        {
            propertyChangedRaised = true;
            propertyChangedEventArgsArgument = e.PropertyName;
        };
        Assert.IsFalse(propertyChangedRaised);
        Assert.IsNull(propertyChangedEventArgsArgument);
        presentationModel.SelectedStock = new PositionSummaryItem();
        Assert.IsTrue(propertyChangedRaised);
        Assert.AreEqual("SelectedStock", propertyChangedEventArgsArgument);
    }

    [TestMethod]
    public void ShouldRaiseOnPropertyChangedEventWhenWatchListSelectedStockUpdated()
    {
        bool propertyChangedRaised = false;
        string propertyChangedEventArgsArgument = null;
        var presentationModel = this.CreatePresentationModel();
        presentationModel.PropertyChanged += (sender, e) =>
        {
            propertyChangedRaised = true;
            propertyChangedEventArgsArgument = e.PropertyName;
        };
        Assert.IsFalse(propertyChangedRaised);
        Assert.IsNull(propertyChangedEventArgsArgument);
        presentationModel.WatchListSelectedStock = new PositionSummaryItem();
        Assert.IsTrue(propertyChangedRaised);
        Assert.AreEqual("WatchListSelectedStock", propertyChangedEventArgsArgument);
    }

    [TestMethod]
    public void ShouldRaiseOnPropertyChangedEventWhenWatchListUpdated()
    {
        bool propertyChangedRaised = false;
        string propertyChangedEventArgsArgument = null;
        var presentationModel = this.CreatePresentationModel();
        presentationModel.PropertyChanged += (sender, e) =>
        {
            propertyChangedRaised = true;
            propertyChangedEventArgsArgument = e.PropertyName;
        };
        Assert.IsFalse(propertyChangedRaised);
        Assert.IsNull(propertyChangedEventArgsArgument);
        presentationModel.WatchList = new ObservableCollection<PositionSummaryItem>();
        Assert.IsTrue(propertyChangedRaised);
        Assert.AreEqual("WatchList", propertyChangedEventArgsArgument);
    }

    [TestMethod]
    public void 
      ShouldNotThrowExceptionIfWatchListIsNullWhileExecutingAddStockToWatchListCommand()
    {
        var presentationModel = this.CreatePresentationModel();
        presentationModel.SelectedStock = null;
        presentationModel.AddStockToWatchList.Execute(null);
    }

    [TestMethod]
    public void 
    ShouldNotThrowExceptionIfSelectedStockIsNullWhileExecutingAddStockToWatchListCommand()
    {
        var presentationModel = this.CreatePresentationModel();
        presentationModel.SelectedStock = new PositionSummaryItem();
        presentationModel.WatchList = null;
        presentationModel.AddStockToWatchList.Execute(null);
    }

    [TestMethod]
    public void ShouldAddStockInWatchListIfSelectedStockIsDoesNotExistInWatchList()
    {
        var presentationModel = this.CreatePresentationModel();
        var selectedStock = new PositionSummaryItem();
        presentationModel.SelectedStock = selectedStock;
        Assert.IsTrue(0 == presentationModel.WatchList.Count);
        presentationModel.AddStockToWatchList.Execute(null);
        Assert.IsTrue(1 == presentationModel.WatchList.Count);
        Assert.AreSame(selectedStock, presentationModel.WatchList[0]);
    }

    [TestMethod]
    public void ShouldNotAddStockInWatchListIfSelectedStockAlreadyExistInWatchList()
    {
        var presentationModel = this.CreatePresentationModel();
        var selectedStock = new PositionSummaryItem();
        presentationModel.SelectedStock = selectedStock;
        presentationModel.WatchList.Add(selectedStock);
        Assert.IsTrue(1 == presentationModel.WatchList.Count);
        presentationModel.AddStockToWatchList.Execute(null);
        Assert.IsTrue(1 == presentationModel.WatchList.Count);
    }

    [TestMethod]
    public void 
    ShouldNotThrowExceptionIfWatchListIsNullWhileExecutingRemoveStockToWatchListCommand()
    {
        var presentationModel = this.CreatePresentationModel();
        presentationModel.WatchListSelectedStock = null;
        presentationModel.RemoveStockFromWatchList.Execute(null);
    }

    [TestMethod]
    public void 
    ShouldNotThrowExceptionIfSelectedStockIsNullWhileExecutingRemoveStockToWatchListCommand()
    {
        var presentationModel = this.CreatePresentationModel();
        presentationModel.SelectedStock = new PositionSummaryItem();
        presentationModel.WatchListSelectedStock = null;
        presentationModel.RemoveStockFromWatchList.Execute(null);
    }

    [TestMethod]
    public void ShouldRemoveSelectedStockFromWatchListIfItExistsInWatchList()
    {
        var presentationModel = this.CreatePresentationModel();
        var selectedStock = new PositionSummaryItem();
        presentationModel.WatchListSelectedStock = selectedStock;
        presentationModel.WatchList.Add(selectedStock);
        presentationModel.RemoveStockFromWatchList.Execute(null);
        Assert.IsTrue(0 == presentationModel.WatchList.Count);
    }

    [TestMethod]
    public void ShouldNotRemoveSelectedStockFromWatchListIfItDoesNotExistsInWatchList()
    {
        var presentationModel = this.CreatePresentationModel();
        var selectedStock = new PositionSummaryItem();
        presentationModel.SelectedStock = selectedStock;
        presentationModel.WatchList.Add(new PositionSummaryItem());
        Assert.IsTrue(1 == presentationModel.WatchList.Count);
        presentationModel.RemoveStockFromWatchList.Execute(null);
        Assert.IsTrue(1 == presentationModel.WatchList.Count);
    }

    [TestMethod]
    public void AddStockToWatchListCanExecuteShouldReturnFalseIfSelectedStockIsNull()
    {
        var presentationModel = this.CreatePresentationModel();
        presentationModel.SelectedStock = null;
        Assert.IsFalse(presentationModel.AddStockToWatchList.CanExecute(null));
    }

    [TestMethod]
    public void AddStockToWatchListCanExecuteShouldReturnTrueIfSelectedStockIsNull()
    {
        var presentationModel = this.CreatePresentationModel();
        presentationModel.SelectedStock = new PositionSummaryItem();
        Assert.IsTrue(presentationModel.AddStockToWatchList.CanExecute(null));
    }

    [TestMethod]
    public void 
    RemoveStockToWatchListCanExecuteShouldReturnTrueIfWatchListSelectedStockIsNull()
    {
        var presentationModel = this.CreatePresentationModel();
        presentationModel.WatchListSelectedStock = new PositionSummaryItem();
        Assert.IsTrue(presentationModel.RemoveStockFromWatchList.CanExecute(null));
    }

    [TestMethod]
    public void 
    RemoveStockToWatchListCanExecuteShouldReturnFalseIfWatchListSelectedStockIsNull()
    {
        var presentationModel = this.CreatePresentationModel();
        presentationModel.WatchListSelectedStock = null;
        Assert.IsFalse(presentationModel.RemoveStockFromWatchList.CanExecute(null));
    }

    [TestMethod]
    public void SubmitCommandCanExecuteShouldReturnTrueIfSearchStringIsNull()
    {
        var presentationModel = this.CreatePresentationModel();
        presentationModel.SearchString = "test";
        Assert.IsTrue(presentationModel.SubmitCommand.CanExecute(null));
    }

    [TestMethod]
    public void SubmitCommandCanExecuteShouldReturnFalseIfSearchStringIsNull()
    {
        var presentationModel = this.CreatePresentationModel();
        presentationModel.SearchString = null;
        Assert.IsFalse(presentationModel.SubmitCommand.CanExecute(null));
    }

    [TestMethod]
    public void SubmitCommandCanExecuteShouldReturnFalseIfSearchStringIsEmpty()
    {
        var presentationModel = this.CreatePresentationModel();
        presentationModel.SearchString = string.Empty;
        Assert.IsFalse(presentationModel.SubmitCommand.CanExecute(null));
    }

    [TestMethod]
    public void SubmitCommandExecutedShouldFilterTheRecords()
    {
        List<PositionSummaryItem> accountSummaryList = new List<PositionSummaryItem>();
        accountSummaryList.Add(new PositionSummaryItem(){TickerSymbol = "stock1"});
        accountSummaryList.Add(new PositionSummaryItem() { TickerSymbol = "microsoft" });
        accountSummaryList.Add(new PositionSummaryItem() { TickerSymbol = "office" });
        accountSummaryList.Add(new PositionSummaryItem() { TickerSymbol = "stock2" });
        this.accountPositionService.List = accountSummaryList;
        var presentationModel = this.CreatePresentationModel();
        Assert.IsTrue(4 == presentationModel.CurrentStocks.Count);
        presentationModel.SubmitCommand.Execute("stock");
        Assert.IsTrue(2 == presentationModel.CurrentStocks.Count);
    }

    [TestMethod]
    public void SubmitCommandExecutedShouldFilterTheRecordsAndCosiderCapitalCase()
    {
        List<PositionSummaryItem> accountSummaryList = new List<PositionSummaryItem>();
        accountSummaryList.Add(new PositionSummaryItem() { TickerSymbol = "stock1" });
        accountSummaryList.Add(new PositionSummaryItem() { TickerSymbol = "microsoft" });
        accountSummaryList.Add(new PositionSummaryItem() { TickerSymbol = "office" });
        accountSummaryList.Add(new PositionSummaryItem() { TickerSymbol = "stock2" });
        this.accountPositionService.List = accountSummaryList;
        var presentationModel = this.CreatePresentationModel();
        Assert.IsTrue(4 == presentationModel.CurrentStocks.Count);
        presentationModel.SubmitCommand.Execute("STOCK");
        Assert.IsTrue(2 == presentationModel.CurrentStocks.Count);
    }

    private StockDetailsPresentationModel CreatePresentationModel()
    {
        return new StockDetailsPresentationModel
		(this.stocksDetailsView, this.accountPositionService);
    }
}

You can see that while testing the view model, I mocked the application services as well as view.

Conclusion

It is very easy to create a testable application with a little bit of extra effort. It would be very fruitful in the long term so now onwards when you write the code, take some time and think if you are writing the testable code.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here