Connect

  • Connect with me

  • Tuesday, May 17, 2011

    Silverlight : Unit Test ViewModel

    Background

    In the previous post I demonstrated how to integrate the MVVMLight toolkit into a Silverlight project. As I said in earlier posts that view models help us in separating the concerns. We can test the view model irrespective of the view. In this post I am going to demonstrate how to unit test the view model. I am going to reuse the example from the previous post and refactor it wherever required.

    Silverlight unit testing framework

    There are some constraints while working with Unit Tests and Silverlight framework. Visual Studio has built in support for unit testing.By default the Visual Studio test runner uses the framework libraries from the full blown version of .Net. Since Silverlight is a scaled down version of .Net framework we cannot use all the types from the .Net framework. Because of this reason Microsoft came up with the additional unit testing framework and a project template specifically for Silverlight. You can read about it here.

    Limitations of Silverlight unit testing framework

    When we are doing Test Driven Development (TDD), its important that we know how much code is covered as part of running the unit tests. This helps us to identify the untested pieces of code and is generally referred to as Code Coverage. The normal Visual Studio unit test framework has very good code coverage feature. But its not supported currently in the Silverlight unit testing framework. Due to this reason we cannot gauge the code coverage and it can lead to untested code being pushed to production environment which can result into bugs.

    Steps to overcome the limitations of Silverlight Unit Testing Framework

    • Extract view model into a separate assembly

    We can tweak the solution and its project structure a bit to get the Visual Studio code coverage working for us. First thing we need to do is to separate the view models into its own assembly. Here again there is a limitation that Silverlight cannot reference assemblies of all types. So we need to create a project of type Silverlight class library. It is in fact one of the best practice to separate view models into their own assembly. This can be helpful in avoiding view specific code like MessageBoxes and Navigation related code slipping into view model unknowingly.

    So lets refactor our example for the previous demo. Open the solution and add a new project of type Silverlight class library and name it ViewModels. Move the MainViewModel.cs and IDialogService.cs files to this new project. If you try to build the solution now, you’ll have few build errors. Add the reference to MVVMLight dll to the ViewModels project. Also the Silverlight getting started reference needs to be updated with the ViewModels dll.

    Once the references are updated, we need to modify the MainViewModel a little bit. In the current situation the default constructor is passing in a concrete instance of DialogService. Earlier this used to work because all the classes and interface were in the same namespace. Now that the namespaces have changed, we need a way of removing this dependency. We’ll remove the default constructor and keep only the one which takes IDialogService as a parameter. We cannot continue with the default constructor supplying the instance of DialogService as it creates a circular reference between the view and the view model.

    That is precisely the reason I have left the implementation of DialogService and the ViewModelLocator within the view layer itself. By doing this we can share the view model between lets say a Silverlight and a Windows Phone 7 applications. Only the view specific things will need to be implemented in the view layer.

    Since we removed the default constructor from the view model, there should be some way of passing the DialogService to it. We modify the ViewModelLocator because that is currently acting like a repository of the view models. Since ViewModelLocator and the DialogService are in the same namespace we can create an instance of DialogService and pass it to the MainViewModel at the time of construction as

                _main = new MainViewModel(new DialogService());

    After making these changes the solution should build properly. In case it doesn’t there must be some problem with the references.




    • Create Unit Test project


    Add a Test project to the solution and name it as ViewModel.UnitTest. Make sure that it is of type Test and not Silverlight Unit Test.


    image


    Add a new class called MainViewModelTest.cs which we’ll use to unit test the MainViewModel. We’ll mock the DialogService and for that we can use any of the commonly available mocking frameworks. I chose to use RhinoMocks. Like we added the MVVMLight toolkit using the NuGet package manager, we add the RhinoMocks dll as well.


    image


    Lets write our first unit test. To keep things simple, I am going to test the MainViewModel’s constructor. If the MainViewModel instance was created successfully, I can ensure that the default properties are set correctly and also the delegate command is created.

            private MainViewModel _mainViewModel;         private IDialogService mockDialogService;         [TestInitialize]         public void Setup()         {             mockDialogService = MockRepository.GenerateStub<IDialogService>();             _mainViewModel = new MainViewModel(mockDialogService);         }         [TestMethod]         public void ConstructorTest()         {             Assert.IsNotNull(_mainViewModel);             Assert.IsNotNull(_mainViewModel.SearchCommand);             Assert.IsTrue(string.IsNullOrEmpty(_mainViewModel.SearchText));             Assert.IsFalse(_mainViewModel.HasText);         }



    • Update reference assemblies


    You can build the solution now. I got a build error because of the version of the assembly which is referenced by default for the Test project. We need to make sure that the System.dll, System.Core.dll and System.Windows.dll are referenced from the Silverlight SDK folder and not from the .Net framework folder.


    image


    From the references delete the System.dll, System.Core.dll and System.Windows.dll (if present). Add these dll from the Silverlight folder which is located at “C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\Silverlight\v4.0” on my machine.


    image


    Once the dll’s are added change the properties of these dll to copy them as local.


    image


    Now if we build the solution, it should build without any build errors. We can run the constructor test and verify that is passes. Once we have the running test, the last thing is to enable the code coverage for the ViewModel project. To enable code coverage, edit the local test run settings


    image


    In the test setting dialog, select Data and Diagonostics options and enable the covergae as shown below


    image


    We are almost there. Once the coverage is enabled, we can decide which all dll’s should be considered for the calculation of the code coverage. This is done using the Configure option when the Coverage is enabled. Enable coverage only for ViewModels.dll


    image


    Thats it. We have enabled coverage for the viewmodels project. Running the test now will tell how what percentage of the code has been covered. Lets run the test and see the results.


    image


    We can see that overall 51% of the code has been covered as part of running the constructor test. From the result we can see that the Setter for HasText and SearchText has not been covered so far. Lets add a test to set the SearchText and see how does it impact the coverage.

            [TestMethod]         public void MainViewModel_WhenSearchTextIsSet_ReturnHasTextAsTrueTest()         {             _mainViewModel.SearchText = "Silverlight";             Assert.IsFalse(string.IsNullOrEmpty(_mainViewModel.SearchText));             Assert.IsTrue(_mainViewModel.HasText);         }

    Lets run all the tests again and see the code coverage.


    image


    With the addition of our new test we can see that the coverage now is 72%. I’ll leave it to the readers to write tests for the remaining uncovered pieces of code as an exercise Smile


    Conclusion


    In this post we saw that with little bit of tweaks here and there it is possible to use the Visual Studio Code Coverage with Silverlight application to unit test the view models for Silverlight. I hope that the Silverlight unit testing framework will soon enable this feature as it is very important and helpful in writing quality code using unit tests. There is nothing wrong in using the Silverlight Unit Testing framework. It works seamlessly with Visual Studio. One problem I have seen is that the unit tests written using Silverlight Unit Testing framework cannot be run as part of continuous integration process. They need to be run manually.


    Compared to that the approach I have demonstrated here can be used to automate the tests as part of continuous build. Personally I feel that’s the biggest advantage using this approach. Hope this helps.


    As always I have uploaded the working solution to dropbox. Until next time Happy Programming Smile

    1 comment:

    1. Hey,

      I was trying to follow your tutorial for unit testing Silverligth application and I am stuck at one point. You have said that the ViewModelLocator should instantiate and pass an instance of IDialogeService. That seems fine, as long as your view model deos not require, say RIA services DomainContext. So, in my case, I have a RIA domain service which my web application is exposing for silverlight to consume. Now my view model was interacting with this domain service to get data form the backend. I am trying to instantiate my view model passing an instance of the domain service to it, but as ow its moved to a different silverlight class lib, I am not able to compile it as it does not find any reference to MyDomainContext.

      Any suggestion what am I doing wrong here. I could write an interface which probably has methods that I need to call on the domain service and then in turn delegate the calls to the domain context form the concrete class, but then I will have to make my domain context implement an interface. As its an auto generated code, I don't want to do that.

      ReplyDelete