Saturday, June 12, 2010

Silverlight Asynchronous Unit Test MVVM and RIA Services

Introduction

I am building up a Silverlight application and we needed to incorporate unit testing in our project. Specifically we needed to unit test our asynchronous RIA Services method calls. I needed to find some examples of this and did not find any really good clean examples. There is really nothing that just gives you a simple example of how to get started.

I was able to get things working and I decided to put together three very basic examples that will give you a place to start from. The first two just give you an understanding of how things work and the third will test a RIA method that resides in a ViewModel.

It is possible to call asynchronous RIA methods inside of a Silverlight unit test without doing what I am about to show you. However doing it this way you can:

  • Really quickly build up unit tests that use your ViewModels.
  • Allow you to capture the full duration of the unit.
  • Avoid issues with asserts failing in asynchronous callbacks which will result in all the unit tests stopping.

Setting Up Asynchronous Unit Test

The steps for setting up an asynchronous unit test are pretty simple:

  • Add a unit test to your Silverlight unit test project.
  • Modify the unit test to inherit from Microsoft.Silverlight.Testing.SilverlightTest.
  • Decorate your test method with Microsoft.Silverlight.Testing.Asynchronous.

Building Up Asynchronous Unit Test

When you inherit from SilverlightTest you will have access to some methods that will help you build up your asynchronous unit test.

  • EnqueueTestComplete() – This method should be used when the test is complete. You can place in your call backs and this will signify the test is over.
  • EnqueueCallback() – This method adds a Callback method into the test queue.
  • EnqueueConditional() – This basically tells the test queue to wait until the condition is true.
  • EnqueueDelay() – Add a delay before continuing to evaluate work items.
  • EnqueueWorkItem – This adds a task to the task queue.

Example 1 – Simple RIA Callback

Here is my first simple example. In this sample we will:

  • Log into Silverlight application.
  • Then call my RIA method to perform a search.
  • Then in the callback of the search, do a check to see if results were returned.
  • All I need to do is add EnqueueTestComplete() into my callback.
[TestClass]
public class Tests : SilverlightTest
{
MyBusinessContext context;

[Asynchronous]
[TestMethod]
public void TestMethod1()
{
LoginParameters userCreds = new LoginParameters("UserName", "Password");
WebContext.Current.Authentication.Login(userCreds, LoginOperation_Completed, null);
}

private void LoginOperation_Completed(LoginOperation loginOperation) {

context = new MyBusinessContext();

context.Load<SearchResult>(context.SearchQuery("foo", "bar", "blah"), SearchCompleted, null);
}

private void SearchCompleted(LoadOperation<SearchResult> loadOperation) {
if (loadOperation.HasError)
{
loadOperation.MarkErrorAsHandled();
Assert.Fail(loadOperation.Error.Message);
}
else {
Assert.IsTrue(context.SearchResults.Count > 0);
}

this.EnqueueTestComplete();
}

}

Example 2 – Multiple RIA Callbacks

Here on my second example, let’s add search two callbacks.

You will notice in this one I use EnqueueConditional() to evaluate that each search callback work item is completed. Then I call EnqueueComplete() after both of the EnqueueConditional() methods have been called.

[TestClass]
public class Tests : SilverlightTest
{
MyBusinessContext context;
bool blnSearch1Complete = false;
bool blnSearch2Complete = false;

[Asynchronous]
[TestMethod]
public void TestMethod1()
{
LoginParameters userCreds = new LoginParameters("UserName", "Password");
WebContext.Current.Authentication.Login(userCreds, LoginOperation_Completed, null);
}

private void LoginOperation_Completed(LoginOperation loginOperation) {

context = new MyBusinessContext();

context.Load<SearchResult>(context.SearchQuery("foo", "bar", "blah"), Search1Completed, null);
context.Load<SearchResult>(context.SearchQuery("blah", "yop", "flop"), Search2Completed, null);

this.EnqueueConditional(() => blnSearch1Complete);
this.EnqueueConditional(() => blnSearch2Complete);
this.EnqueueTestComplete();
}

private void Search1Completed(LoadOperation<SearchResult> loadOperation)
{
if (loadOperation.HasError)
{
loadOperation.MarkErrorAsHandled();
Assert.Fail(loadOperation.Error.Message);
}
else {
Assert.IsTrue(context.SearchResults.Count > 0);
}

blnSearch1Complete = true;
}

private void Search2Completed(LoadOperation<SearchResult> loadOperation)
{
if (loadOperation.HasError)
{
loadOperation.MarkErrorAsHandled();
Assert.Fail(loadOperation.Error.Message);
}
else
{
Assert.IsTrue(context.SearchResults.Count > 0);
}

blnSearch2Complete = true;
}

}

Example 3 – Testing ViewModel Asynchronous Methods

So now you may be wondering how to test ViewModels with your RIA method calls. Here is a simple ViewModel that performs a search which could be bound to a Silverlight control.

  • You see the search callback I part of the ViewModel.
  • There are IsSearching and IsSearchComplete properties that indicate the status of the search.
  • There is a property that returns a collection of search results.

public class MyViewModel
{
MyBusinessContext _context;
bool _IsSearchComplete = false;

public MyViewModel() {
_context = new MyBusinessContext();
}

public void Search(string Criteria1, string Criteria2, string Criteria3)
{
_IsSearchComplete = false;
_context.SearchResults.Clear();
_context.Load<SearchResult>(_context.SearchQuery(Criteria1, Criteria2, Criteria3), SearchCompleted, null);
}

private void SearchCompleted(LoadOperation<SearchResult> loadOperation)
{
if (loadOperation.HasError)
{
loadOperation.MarkErrorAsHandled();
}

_IsSearchComplete = true;
}

public EntitySet<SearchResult> GeSearchResults {
get {
return _context.SearchResults;
}
}

public bool IsSearching {
get {
return _context.IsLoading;
}
}

public bool IsSearchComplete {
get {
return _IsSearchComplete;
}
}
}


The following is a Silverlight Unit Test that will test this asynchronous method call:

  • First we create an instance of the ViewModel.
  • I then use the EnqueueCallback() method to call the Search method which has an asynchronous callback within the ViewModel. This basically puts the method onto the queue.
  • Then I call EnqueueConditional() where I set the ViewModel.IsSearchComplete to be evaluated to check to see of the asynchronous search operation has been completed. This basically blocks the queue from continuing until this is true.
  • Next I call EnqueueCallback() again and set an Assert to check to see if search results were returned. This assert will be evaluated after the EnqueueConditional() is complete.
  • Finally EnqueueTestComplete() is called to tell the queue that testing is over.

[TestClass]
public class Tests : SilverlightTest
{

[Asynchronous]
[TestMethod]
public void TestMethod1()
{
LoginParameters userCreds = new LoginParameters("UserName", "Password");
WebContext.Current.Authentication.Login(userCreds, LoginOperation_Completed, null);
}

private void LoginOperation_Completed(LoginOperation loginOperation) {

MyViewModel myViewModel = new MyViewModel();
EnqueueCallback(() => myViewModel.Search("foo", "bar", "blah"));
EnqueueConditional(() => myViewModel.IsSearchComplete);
EnqueueCallback(() => Assert.IsTrue(myViewModel.GeSearchResults.Count > 0));
EnqueueTestComplete();

}

}

Conclusions

Once I got the hang of this, I really saw how simple it is to put together good unit tests that exercise my Silverlight ViewModels and RIA Service calls.

References

No comments: