I’ve just started to set up some unit tests for a pet project I’m playing with to wrap the GoogleReader.API for Silverlight clients.
Once you’ve downloaded the latest Silverlight Toolkit you’ll have access to the Silverlight Unit Testing Framework – the great thing about this framework is that it makes writing tests that call asynchronous methods a breeze – well, I was actually becalmed for a while until I worked out a few tricks to get things working.
The framework lets me write tests that look something like,
[TestClass]
public class CommandTests: SilverlightTest
{
[Asynchronous]
[TestMethod]
public void Can_Login()
{
ConnectCommand command = new ConnectCommand();
command.BeginConnect("myemail", "mypassword", (result) =>
{
Assert.IsTrue(result.Result && result.Sid != null && result.AuthToken != null, "Can't log in");
EnqueueTestComplete();
});
}
}
You simply add the Asynchronous attribute and make sure you call EnqueueTestComplete to let the framework know the test is complete. Easy.
Except every time my test failed Visual Studio would treat this as an unhandled exception, my test would bomb but it wouldn’t show up as a failed test in the test UI harness.
It turned out not to be the fault of Visual Studio or the framework – it was me!
My implementation of the BeginConnect method ultimately had some code that did,
if (callback != null)
{
callback(resultSet);
}
Which resulted in my callback method being executed on the thread of my asynchronous method call rather than on the UI thread. This in turn meant the unit testing framework (which is running on the UI thread) couldn’t handle the failed assertions. Bingo!
One solution was to re-implement my component’s code to,
if (callback != null)
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
callback(resultSet);
});
}
Which I’m not entirely comfortable with since my component is not a UI component so shouldn’t really have to deal with this.
The alternative was for my test method to make the assertions on the UI thread by changing them to,
[Asynchronous]
[TestMethod]
public void Can_Login()
{
ConnectCommand command = new ConnectCommand();
command.BeginConnect("myemail", "mypassword", (result) =>
{
TestPanel.Dispatcher.BeginInvoke(() =>
{
Assert.IsTrue(result.Result && result.Sid != null && result.AuthToken != null, "Can't log in");
EnqueueTestComplete();
});
});
}
Where TestPanel is the UI element hosting the test results and is exposed through the PresentationTest base test class.
This approach is probably cleaner and could be abstracted more – perhaps into a specialisation of the PresentationTest class itself.
Anyone got any better ideas?