Friday, September 24, 2010

Getting more out of unit testing in Silverlight

As I already mentioned in my previous post, we're building a line-of-business app using Silverlight 4 and WCF Data Services. I really think Silverlight is great for those kind of systems, but since it is based on a different run-time as the full .NET framework, you may wonder what you'll run into when practicing TDD, relying on IoC or using a mocking framework.

Well, both Rhino Mocks and MOQ have a Silverlight version, although I have only used Rhino Mocks myself. Additionally, if you're into IoC containers like me, Microsoft Unity 2.0 has been working perfectly under Silverlight 4. Here are some additional considerations.

In general, I prefer the Arrange-Act-Assert style for state-based tests such as when specifying the behavior of domain entities or simple framework elements. However, for view models or other code that has a more orchestrational nature I prefer to use a style that more closely resembles Behavior Driven Design. Consider for example the specifications for a MVVM class.

    [TestClass]

    public class ManageRegistrationViewModelSpecs

    {
        [TestClass]

        public class When_adding_an_importing_country : SpecificationContext

        {

            private ManageRegistrationViewModel model;

            private Country countryToAdd;

 

            protected override void EstablishContext()

            {

                countryToAdd = new CountryBuilder().Build();

 

                model = new ManageRegistrationViewModelBuilder()

                    .WithCountry(countryToAdd)

                    .Build();

            }

 

            protected override void Because()

            {

                model.SelectedCountry = countryToAdd;

                model.AddCountry();

            }

 

            [TestMethod]

            public void It_should_add_a_new_importing_country_to_the_model()

            {

                model.ImportingCountries.Should().HaveCount(1);

 

                ImportingCountryModel countryModel = model.ImportingCountries.Single();

                countryModel.Country.Should().BeSameAs(countryToAdd);

                countryModel.LicenseType.Should().BeNull();

                countryModel.RegulatoryAgent.Should().BeNull();

                countryModel.MarketingAuthorizationHolder.Should().BeNull();

                countryModel.TradeName.Should().BeEmpty();

            }

 

            [TestMethod]

            public void It_should_queue_the_coresponding_command()

            {

                model.PendingChanges.Should().HaveCount(1);

                model.PendingChanges.Should()
                .ContainItemsAssignableTo<AddImportingCountryCommand>();

 

                var command = (AddImportingCountryCommand)model.PendingChanges.Single();

                command.CountryId.Should().Be(countryToAdd.Id);

            }

 

            [TestMethod]

            public void It_should_remove_that_country_from_the_list_of_available_countries()

            {

                model.Countries.Should().NotContain(countryToAdd);

            }

 

            [TestMethod]

            public void It_should_clear_the_selected_country()

            {

                model.SelectedCountry.Should().BeNull();

            }

        }

    }

Unfortunately, the current Silverlight Unit Test runner does not show the name of the outer test class, in this case ManageRegistrationViewModelSpecs, and I haven't found a solution for this yet. So if your list of tests is extensive, you may have some trouble finding the test your interested in. However, one nice trick I found by inspecting the source code of the test runner is that you can apply an [Exclusive] attribute to a particular class. This ensures that the test runner only runs that particular test. For completeness, here is the code of the SpecificationContext base-class I use:

    [TestClass]

    public class SpecificationContext

    {

        private Exception initializationException;

 

        [TestInitialize]

        public void TestInitialize()

        {

            try

            {

                EstablishContext();

                Because();

            }

            catch (Exception exception)

            {

                initializationException = exception;

            }

        }

 

        [TestMethod]

        public void Establish_context_and_because()

        {

            if (initializationException != null)

            {

                throw initializationException;

            }

        }

 

        [TestCleanup]

        public void TestCleanup()

        {

            CleanupContext();

        }

 

        protected virtual void EstablishContext() {  }

 

        protected virtual void Because() { }

 

        protected virtual void CleanupContext() { }

    }

You may notice the awkward exception handling code in there. I discovered that the current test runner swallows all exceptions that occur in the method marked with the [TestInitialize] attribute. That's why I explicitly catch every exception that occurs while calling EstablishContext and Because and re-throw it in a dedicated method marked with the [TestMethod] attribute. The full .NET Framework does not have this particular symptom, so It's a Silverlight thingy.

Another tip to improve the testing experience is to disable the tag editor that you get when you launch your test project. Replacing the Application_Startup method of the App class with the following lines quiets it down permanently:

    private void Application_Startup(object sender, StartupEventArgs e)

    {

        var settings = UnitTestSystem.CreateDefaultSettings();

        settings.StartRunImmediately = true;

 

        RootVisual = UnitTestSystem.CreateTestPage(settings);

    }

Also, if you apply the Continuous Integration practice, you may want to check out StatLight. I have not managed to get it running myself, but it is supposed to support running Silverlight tests from the command-line or from your favorite build engine.

And finally, if you're really into the testability and object-oriented design principles, you must check out Caliburn Micro. It's an open-source framework for Silverlight 4, Windows Phone 7 and WPF, and has some excellent concepts for relieving the burden of working with asynchronous code. I've been using it for over two months now and I'm exhilarated about its coroutine concept and the way you use it in your tests.