The complexity of the web application has increased. State of the art web applications can compete with the usability of desktop applications while still maintaining the advantages of web applications: Ease of deployment, availability. These kinds of applications are usually referred to as Rich Internet Applications (RIA).
What is Silverlight?
The Microsoft answer for this demand was the creation of Silverlight.
Silverlight is an implementation of Common Language Runtime (CLR) runnable in a web browser.The applications are deployed as XAP packages (zipped DLLs and resources), downloaded from the web and executed.
The applications for Silverlight can be developed in any supported .Net language (C#, VB.NET), and there is support for the scripting languages (like Python or Ruby).
There are significant differences between developing desktop applications and Silverlight applications. The major differences are:
- Security modes/ Sandboxing. While the desktop application could use machine resources, the Silverlight application has significantly restricted access to system resources. This concept is called sandboxing. Silverlight applications cannot use the machine file system without user's consent (all file interaction must be user originated), cannot use active reflection (only read operations are permitted), cannot use registry, or connect to databases. Basically it plays inside its own sandbox with no opportunity to influence anything outside it.
- Asynchronous programming model. All IO operations (including web, networking) are asynchronous. The main reason is to increase system response performance. The UI cannot be blocked by waiting for an IO operation to end.
- Different/missing API. The API available for Silverlight development is only a subset of the API available for desktop. There are some parts missing totally (no support for XSLT, XPath, and DB).
- And, as we mentioned before, all IO APIs only have asynchronous operation available.
The latest version of Silverlight has the option to free itself from the browser and the application can be installed on the user's desktop (out of browser capabilities), subsequent version are planned to allow lower level system interaction (elevated mode) that will bring the Silverlight application even closer to existing desktop applications.
In this tech letter we would like to show what is presented as being the correct way to write Silverlight applications - the model - view - ViewModel design pattern, motivation for its creation, and to present a sample usage. Later I would like to discuss the state of the unit testing for Silverlight applications, its challenges, and present a sample case for the Silverlight unit test.
The article is not meant to be a beginner's introduction to Silverlight; we expect the reader to already be familiar with the basics of Silverlight development techniques ( XAML, code behind, asynchronous programming model) as well as the basics of unit testing (test structure, mocking, and the basics of inversion of control pattern.) For reference please take a look at [TL1] and/or [TL2].
The MVVM pattern
The simplest Silverlight applications use the controls defined in XAML and the application logic written in code behind files. The code logic and visual logic are mixed in the same place. This approach would work for a simple application, but it will introduce problems for more complex ones:
1. The code is hard to test. If the logic is only triggered as the result of UI interaction, then the test will need to host the UI component (and provide relevant context) and trigger these UI events. Also if the results are only visible on the UI level, the tests will need to access the visual properties of the UI element to verify the correctness of the operation. All of this is quite hard to put into unit tests.
2. The code is hard to change. Since the presentation is mixed with the application/domain logic, it is hard to change one without introducing the change to others. Along with poor tests, this renders the application really fragile in terms of change.
3. It breaks designer/developer workflow. The idea is that the designer and developer should be able to work on the same component at the same time - the designer focusing on the visual part and the developer on the logic. This would be close to impossible if they worked on the same file.
The problem of presentation and logic separation is not new to the software industry. The solution for this issue has been the Model-View-Controller (MVC) pattern.
The main idea behind the MVC pattern is to strictly delegate responsibility to the components. The model will represent the data, view the visual part, and the controller will handle data manipulation and view rendering.
The MVVM pattern follows the same idea, but it is more closely tied to the Silverlight capabilities (views are not "rendered" as in classic MVC but rather data-bound to the view models).
Let's have a closer look at these parts.
The model represents real world data that the application presents and manipulates. The model should mainly contain the structure, relations and domain logic. It should not contain any other logic (like services or storage).
In the sample project, the model consists only of the class representing the person:
Views contain visual elements that present the data from the model to the user. The View itself contains only visual logic (click on the button to open drop-down) but no application logic. View is not even responsible for its state - it delegates the logic to view model.
The View is bound to the ViewModel using data-binding. Data binding is the technology where controls do not hold the value of the data; rather they have a link (binding) to the data and are notified when the data has changed (one way binding). Or alternatively the data-bound control will notify the model when the user has changed the value on the UI (two-way binding).
The data binding is not restricted only to the content; any property of the control (any dependency property to be precise) could be data bound. For example, binding to the Visibility property of the control could show/hide the control based on the data.
The action controls (buttons, hyperlinks) could have the command binding. We will discuss commands later in this document.
In the following sample, the view consist of two buttons (Load and Save), some editable fields, and some read only fields for calculated values.
This is the glue component that brings the View and data together. The View should only interact with the ViewModel, and the ViewModel should be the only one interacting with the model. The ViewModel also interacts with the "outer world" - it invokes services and obtaining and manipulating the data.
For ViewModel to be able to notify the view, it must implement the INotifyPropertyChanged interface.
The event notifies the UI about the change in the property.
For notification in the collections, there is an interface with similar purpose:
The ViewModel provides multiple items:
1. For read only controls it provides simple properties.
The ViewModel is responsible for formatting values. It contains not only the real model properties, but also some calculated properties:
2. For read-write controls it provides the property with some logic:
This code has a few interesting points:
- It wraps the property of the model class and does not directly expose it.
- It handles a case when the data are not available yet.
- Setter notifies the UI about the changed value, invokes the validation of the new value.
- It notifies not only about change in the direct property, but also about the change in the related / calculated property (so the UI will reload its value).
3. Manages the view visual state:
In the view (see the previous chapter), the stack panel is inside the busy indicator control. The responsibility of this control is to provide the progress bar and does not allow user interaction with it child elements while the progress bar is visible. The visibility of the progress bar is bound to the IsBusy property of the ViewModel. If it is set to true - the application is busy and the user is blocked; should it be set back to false, the user can interact again.
4. Provides commands:
Commands are the "verbs" for the ViewModel and represent the actions that the ViewModel can do. Commands are the place where interaction with services is triggered. Command must implement the ICommand interface:
Execute is the action executed when the command is being invoked.
The CanExecute method tells the UI whether the command is allowed to be executed in the current state, and the UI will enable or disable (gray out) the bound control accordingly.
The CanExecuteChanged event should be fired to inform the UI about possible changes in command availability and the UI will re-query the state.
In the sample we present two commands: one for loading the data, and the second for saving the data. We use a helper class RelayCommand as the ICommand interface implementation.
In the sample the "load" command:
- Executes the StartLoad method when invoked
- Is available when: we have service, the data has not been loaded yet, and the model/view is not in busy state.
Since the View does not contain any logic, the designer could design the View in the specialized tool (e.g. Expression Blend) without being influenced by the developer who will be working on the ViewModel.
The designer might even be provided with the design-time view model: A ViewModel implementation containing mocked up data to help visualize components in the context.
The developer could work on the ViewModel in parallel. Since the ViewModel is a pure code - it does not contain references to the view - he should be able to author and unit test the ViewModel code in isolation from the designer working on the visual part.
The first and most criticized property of the MVVM pattern is that it is too verbose. Every changeable property needs to fire the property changed event, and in Silverlight there is no simple way to introduce functionality other than to write tons of same code for every property.
Some of the data access libraries for Silverlight (WCF data services, RIA services) generate model classes which already implement the INPC interface. For simpler issues it is viable to directly expose these classes in the ViewModel (which breaks the pattern), because the abilities of the classes supersede what the ViewModel could offer without heavy coding (validation support, collection tracking, to name a few).
The handling of collections could be tricky as well. Some visual components introduce smart functionality to improve performance and memory footprint - namely virtualized panels and/or server side paging/sorting. A not very smart implementation of collection in the ViewModel could render these optimizations useless and badly hurt performance.
Unit testing in Silverlight
Unit testing the Silverlight application is similar to unit testing the regular dot net application. Microsoft provides the unit test framework as part of the free and open-source Silverlight toolkit.
The test framework uses the same approach, attributes, and asserts classes as the MSTest framework known from the desktop .Net framework. However, the tests runner is different. Silverlight tests run inside the Silverlight application displayed on the following picture:
Silverlight test runner showing 3 passed tests.
However, there are some unique challenges to testing the Silverlight application:
1. Bad design (mixing UI and logic):
As we mentioned before, if the logic is strongly dependent on UI interaction and/or UI context it could be hard to isolate and execute.
The isolation of UI, model, and logic as in the MVVM pattern should ease the task at hand. If the UI interaction is necessary, the test framework provides a facility called TestPanel. It is a UI panel assigned for each test where the test can attach UI components when needed.
The tests could be paused and wait for user interaction, or we can use UI automation peers to interact with the UI from the code.
The majority of operations in Silverlight are asynchronous. That fact renders the classic "prepare, execute, verify" approach to unit test needless. For this reason the Silverlight test framework introduces the concept of asynchronous tests.
If the test is annotated with the [Asynchronous] attribute, it is not to be considered ended when the code exits the method.
We can register various callbacks and such will be executed asynchronously after the method exits.
There are multiple kinds of callbacks available:
EnqueueCallback - will execute the registered action asynchronously (on UI thread)
EnqueueConditional - the framework will stop executing registered callbacks until the condition is met. It is mainly used for synchronization purposes.
EnqueueDelay - delays the execution of callbacks for the registered time.
EnqueueTestCompleted - notifies the framework that the test has actually finished.
An example of this will be shown later in the document.
3. Hard to mock some services:
To achieve system isolation from the external parts, we usually leverage mocking frameworks. Multiple well-known mocking frameworks have already been ported for Silverlight (Moq, JustMock). However there are some issues:
- Due to the Silverlight strict security model, mock frameworks are not allowed to perform the same "magic" as their full framework equivalents.
- Data access technologies for Silverlight are usually sealed (prohibiting overriding, which is the usual way mock frameworks operate) so they cannot be easily mocked. The solution seems to be able to use another layer of indirection and wrap those services into another data access layer (repository pattern). However, this will block the other capabilities of the frameworks that are really useful (validation, context tracking).
4. Gathering and analyzing output:
Again due to Silverlight security model, the test framework does not have access to the file system so it is not able automatically save the test results into a file. The test runner provides only the visual representation.
This is a blocker for the test integration.
Until now there is no way to integrate Silverlight unit tests into the TFS build system. (However, there is open-source tool called StatLight that is capable of running Silverlight tests from the console (it runs its own http server as well as providing hosting environment for Silverlight) and providing the test result that the automated environment could use.)
Let's test the operation of loading data from the service.
What we expect to happen:
- When load operation is invoked, the UI enters busy state
- The data are being loaded
- The fields are being populated and the UI will be notified about these changes
- The availability of the buttons will change (load button will become disabled and save button enabled) and UI will be notified about these changes
- The view model will exit busy state
How do we approach testing this scenario?
1. Our sample view model gets the service injected using the inversion of control. When testing, we will inject the mock of the service. Using the Moq framework we can craft such a mock easily. The mock will need to simulate asynchronous call.
2. We will verify that the view model entered busy state.
3. We would like to verify that the model fires all requested events. To verify this, we will wrap the model into the event listener class which will subscribe to all events that the model exposes and monitor them. Later we will compare what was recorded with the set of expected events.
4. We will verify the view model state after the loading finishes (it should exit busy state)
5. We also check the properties of the view model as well as the commands availability.
In the article we presented the Model -View - View model design pattern, the problem that it is trying to address, as well as some challenges related to this pattern. We presented the Silverlight unit test framework, described the main difference from the desktop unit test frameworks, and discussed some troubles involved when working with this framework in Silverlight.
There is also a sample application built using the MVVM pattern along with the basic tests presented in the article to demonstrate the testability of the code.
http://live.visitmix.com/Archive (year 2010, lesson: CL59)