In this post I'll talk about testing the business logic in the view model & the model pieces of the Silverlight app. The other posts in this series are as follows (I'll update this list as they are published):
- Silverlight, MVVM & SharePoint - About this Series...
- Silverlight, MVVM & SharePoint - How I do MVVM
- Silverlight, MVVM & SharePoint - My favorite MVVM toolkit - MVVM Light Toolkit
- Silverlight, MVVM & SharePoint - Working with Commands
- Silverlight, MVVM & SharePoint - Working with Messages
- Silverlight, MVVM & SharePoint - Working with Dialogs
- Silverlight, MVVM & SharePoint - Testing the Business Logic
- Silverlight, MVVM & SharePoint - Testing the User Interface
- Silverlight, MVVM & SharePoint - Using Data Services
MVVM at it's core is a design pattern that is a guideline on how to separate your UI from the business logic in XAML based applications. One of the biggest benefits to this is the ability to fully test your application's business logic because 100% of it does not live in the user interface. This is incredibly powerful for reasons I really shouldn't have to enumerate.
However there are some tricks to unit testing Silverlight applications. The biggest challenge with testing Silverlight applications is that all their network communication occurs asynchronously. This presents a problem because most likely the tests complete before the app gets a response and thus you can't accurately test the calls.
The Silverlight Toolkit on CodePlex has pieces that aim to solve this. In fact if you download the source code you'll see they are using the Silverlight Unit Test Framework (an unfortunate acronym of the "SLUT Framework"). This framework helps with asynchronous tests. Unfortunately it gets a tad bit messy when there's lots of stuff going on and that's when mocking frameworks may help.
I'm going to assume you know how to do simple tests in .NET. For my samples I'm using MSTest which everyone has with Visual Studio and frankly it works good enough... I'll sidestep the debate about which one is best... for now I'll stick with MSTest. In addition, I'm also assuming you're doing all of this in a Silverlight Test project... a regular old Test project simply won't do.
Take a simple test that wants to verify that when a call is made to the view model's PropertyCategoryCollection property, it receives a collection of items. This should always return a collection of categories even if it is initially empty because the internals of the property automatically get the categories from the data source if it is empty. The trick is that if it is empty, while the property will hydrate itself from the data source, it does so asynchronously and thus, the test will finish too quickly to accurately test it.
Here's the solution. First, my test is going to inherit from the Microsoft.Silverlight.Testing.SilverlightTest class and setup the test.
Next we write the test. Let's pick through this line by line...
First, not only do we decorate the method as a test (#25) but we also decorate it as an async test (#26). While in this test it isn't necessary I'm verifying that the MVVM Light Toolkit's DispatcherHelper has been initialized (#28-29) which is what we'd use if we were doing any threading stuff. Next up I'm going to request a reference to the ProductCategoryCollection property. If this is empty, internally my view model should have first set the app to busy (a UI thing that's controlled from the IsMainBusy object) & then started the process of going to the data source to get categories.
At this point I want my test to go into a waiting state until that call is finished... this is where the Silverlight Unit Testing Framework (no, I won't use the acronym) comes into play. There are three methods that start with "Enqueue"... what they do is they control the order of operations with respect to the test. First (#33) I pass in a conditional statement which effectively says "don't continue with the test until the following statement is true" which in this case is "don't continue until the IsWorking property is false" which is set when the view model receives a response from the data source and handles it. Then using EnqueueCallback (#34-39) I'm saying "here's the code to run, which includes my test assertions, after the EnqueueConditional is satisfied." Last but certainly not least, the last step is to tell the framework we're finished with the test by calling EnqueueTestComplete() (#40).
Seems easy enough doesn't it? As you can imagine, stuff can get real complicated REAL fast. For instance what if you want to test getting a product collection back? First you need to get the categories back to select one and get all products that match that category... that's how your app is supposed to work so you should test it, right? So what makes this complicated? Well you are now talking about nesting conditions which can get hard to read, like this example:
The question usually arises "does it make sense to test all this in the view model... shouldn't you just test the data service?" Now we get to deal with the unit testing zelots who strive for 100% code coverage or the other side that says "test enough to provide good enough code coverage." I'll leave it up to you how you want to do it... I'm just showing you how it would work if you were on the 100% side.
In the next post in this series I'll discuss how you can also test the user interface of the application.