In my last post I am going to cover the last two topics I set out to address. The first topic is UI testing. What unit test frameworks lack sometime is the actually UI testing. What I mean by that is for example, click on a button, then see what happens. In case of Silverlight, Silverlight framework itself has a number of helper classes to help us achieve this goal. For example, ButtonAutomationPeer class allows us to drive button actions from within unit tests. Also, Microsoft Silverlight unit testing framework allows developers to insert UI components onto unit testing surface. Let me illustrate both concepts via an example. Here is my code for such unit test”
[TestMethod]
[Asynchronous]
[Description("Asynch Get Companies in UI")]
[Tag("Asynch")]
public void TestUIGetCompanies()
{
var companyVM = new CompanyModule.ViewModels.CompanyListViewModel(
new Microsoft.Practices.Composite.Events.EventAggregator());
CompanyListView companyV = new CompanyListView();
companyV.DataContext = companyVM;
TestPanel.Children.Add(companyV);
ButtonAutomationPeer buttonPeer =
new ButtonAutomationPeer(companyV.FindName("GetCompaniesButton") as Button);
DataGrid grid = companyV.FindName("CompanyGrid") as DataGrid;
IInvokeProvider buttonInvoker = (IInvokeProvider)buttonPeer;
IGridProvider gridPeer = new DataGridAutomationPeer(grid);
EnqueueCallback(() => buttonInvoker.Invoke());
EnqueueConditional(() => { return companyVM.CompanyList != null; });
EnqueueCallback(
() => Assert.IsTrue(companyVM.CompanyList.Count() > 0, "Should have data"),
() => Assert.IsTrue(grid.Columns.Count == 3, "Should have 3 columns"),
() => Assert.IsTrue(gridPeer.RowCount > 0, "Should have rows"));
EnqueueTestComplete();
}
Let’s trace down what is going on in this test. First of all, I am creating view model class. Since I do not have a bootstrapper, I manually create EventAggregator myself. Next, I am create a view to put my view model into. I create view, then put my view model into the data context of my view. Nest important step is to add my view to unit te3swt harness. This is accomplished by adding my view to test panel. My screenshot below illustrates where test panel is in terms of test runnier UI. I added hand cursor to illustrate the test panel referred to in UI below as “Test Stage.” As I run the unit tests, I will actually see my view there in the test stage. Just like I added the view, I can remove it from test stage.
Next I am setting up code to invoke a button. I am creating new instance of ButtonAutomationPeer class. Of course, I need to find a button to attach the automation peer to, and I am using FindName method to find an i9nstance of my button. To use this functionality I had to name my button. Then, I am setting up an automation peer for my grid as well. In general, most user input controls in Silverlight framework have matching automation objects. In my case, I am testing the results of my button invocation directly against the grid, making sure that the grid has correct number of rows and columns. I can also combine this type of testing with mocking, Here is the unit test for this case:
[TestMethod]
[Asynchronous]
[Description("Asynch Get Companies in UI With Mocking")]
[Tag("Asynch")]
public void TestUIGetCompaniesWithMock()
{
var companyVM = new Mock<ICompanyListViewModel>();
companyVM.SetupGet(testVM => testVM.CompanyList).
Returns(new ObservableCollection<Company>(new[] {
new Company(){
CompanyID = Guid.NewGuid(), DateAdded = DateTime.Now, CompanyName = "some random company"},
new Company(){
CompanyID = Guid.NewGuid(), DateAdded = DateTime.Now.AddDays(-1), CompanyName = "another company"}}));
companyVM.SetupGet(testVM => testVM.GetCompaniesCommand).
Returns(new DelegateCommand<object>((o) =>
companyVM.Raise(vm =>
vm.PropertyChanged += null, new System.ComponentModel.PropertyChangedEventArgs("CompanyList"))));
CompanyListView companyV = new CompanyListView();
companyV.DataContext = companyVM.Object;
TestPanel.Children.Add(companyV);
ButtonAutomationPeer buttonPeer = new ButtonAutomationPeer(companyV.FindName("GetCompaniesButton") as Button);
DataGrid grid = companyV.FindName("CompanyGrid") as DataGrid;
IInvokeProvider buttonInvoker = (IInvokeProvider)buttonPeer;
IGridProvider gridPeer = new DataGridAutomationPeer(grid);
EnqueueCallback(() => buttonInvoker.Invoke());
EnqueueCallback(
() => Assert.IsTrue(companyVM.Object.CompanyList.Count() > 0, "Should have data"),
() => Assert.AreEqual(3, grid.Columns.Count, "Should have 3 columns"),
() => Assert.AreEqual(2, gridPeer.RowCount, "Should have rows"));
EnqueueTestComplete();
}
In the case above I am mocking property changed event by invoking it when a button is clicked, causing UI to re-bind. The rest of testing code is very similar to actual invocation of server side code.
The last topic I would like to cover is StatLight framework. You can take a closer look at the framework here. This framework allows developers to invoke unit tests written with Microsoft unit testing framework for Silverlight from a command line and also read in results of such runs. Once you download and unzip the DLLs into a folder, you will need to do one more thing – Unblock those DLLs in the properties of each file in Windows explorer. Also, if you want to test server side code using StatLight, you will need to convert your web site to use local IIS inste4ad of Cassini (Visual Studio built-in IIS Server). You will also need to put clientaccesspolicy.xml into the root of your IIS in order to avoid cross domain exceptions. Using the StatLight framework is very simple. Simply call StatLight.exe and pass XAP file on command line that contains your unit tests. Here is a screenshot of what it looks like:
As you can see, your –x parameter is the only one you have to specify to test. You will also see that unit test is launched at the same time. Now, to test results you have to add one more parameter – r. Here is what that command would look like:
Once you run this, you will end up with out.txt in the specified folder. Here is the content of this file:
The last parameter I want to mention is ability to limit test run by tag attribute I covered in an earlier post. To user this feature, you have to add –t command line attribute like so: Here is what this command line look like:
You can download complete solution here.
Thanks.
How do you use statlight with an IIS deployment? There is mention of it in the coverage on Statlight at the end, but no examples are given. In my case, unit tests need database access and to do that they need to call to *.svc end points on a web server deployment. Any ideas on how this is possible?
Thank you,
Aaron
In my case at the start of each test (test class) I setup client that communicates with the server and code its Url to http:localhost/SomeVirtualDir/SomeSvc.svc
You just have to make sure to setup IIS on the machine that runs tests (or machine that tests communicate with – instead of local host use actual server Url).
It looks like StatLight has some issues with testing RIA Service methods which require authentication:
http://statlight.codeplex.com/discussions/226489
WebContext instance can be initialized in the App constructor only, or in Xaml.
StatLight runs in the context of its own host Silverlight application.