Monday, 19 May 2008

MVC Architecting Silverlight Applications Part 3 - Testing the ViewModel

In Part 2 of this series, I built a small class used to model our Login page - LoginViewModel.

In this post, I will demonstrate how to unit test this class to show that it behaves as expected. This is pretty essential as we want to be sure the class works properly before handing it over to our designer to build a view for!

I will be using TestDriven.NET's new Silverlight unit testing support which I blogged about earlier. This is unbelievably simple - just download and install the project template and then create a new Silveright NUnit Project called Silversocial.Client.Modules.Test

Building some test doubles


Before we can start unit testing our LoginViewModel, we need to fake out the dependencies. Given that there currently exists no mocking framework for Silverlight ( are you listening Ayende? ;-> ) we will manually build some stubs for our dependencies and then reuse them in our tests.

It is quite easy to way manually to build reusable stubs for an interface of your choosing. I simply follow these rules:
  • Use "explicit interface implementations" for all members

  • Implement properties as normal properties with a backing field (or as automatic properties if you'd prefer)

  • Implement methods by delegating the entire method body to a public event handler with the same signature and name as the method. Set the event handler to empty delegate to avoid null checks

  • Implement events by delegating the add/remove to a public event handler with the same signature as the event. Set the event handler to empty delegate to avoid null checks


Here is the code for the stub for the IAppShell interface.

using Silverstone;

 

namespace Silversocial.Client.Modules.Tests.Stubs

{

    public class AppShellStub : IAppShell

    {

        private User user;

        private IView view;

 

        public User User

        {

            get { return this.user; }

        }

 

        public IView View

        {

            get { return this.view; }

        }

 

        User IAppShell.User

        {

            get { return this.user; }

            set { this.user = value; }

        }

 

        void IShell.SetView(IView view)

        {

            this.view = view;

        }

    }

}


And now the stub for the ILoginDataProvider, also following the guidelines set above:

using System;

 

namespace Silversocial.Client.Modules.Tests.Stubs

{

    public class LoginDataProviderStub : ILoginDataProvider

    {

        private EventHandler<ValidateUserCompletedEventArgs> validateUserCompleted = delegate { };

 

        public Action<User> ValidateUser = delegate { };

 

        public void RaiseValidateUserCompleted(ValidateUserCompletedEventArgs args)

        {

            this.validateUserCompleted(this, args);

        }

 

        event EventHandler<ValidateUserCompletedEventArgs> ILoginDataProvider.ValidateUserCompleted

        {

            add { this.validateUserCompleted += value; }

            remove { this.validateUserCompleted -= value; }

        }

 

        void ILoginDataProvider.ValidateUser(User user)

        {

            this.ValidateUser(user);

        }

    }

}


We will create a "ViewStubBase" which implements the IView interface itself. This will form the base class for all other stubs of the views.

using System;

using Silverstone;

 

namespace Silversocial.Client.Modules.Tests.Stubs

{

    public abstract class ViewStubBase : IView

    {

        public Action OnLoad = delegate { };

        public Action OnUnload = delegate { };

 

        void IView.OnLoad()

        {

            this.OnLoad();

        }

 

        void IView.OnUnload()

        {

            this.OnLoad();

        }

    }

}


And now the stub for the ILoginView interface. As you can see we just implement the LoginUnsuccessful method and call a public event handler which does nothing by default. The allows the test to listen to the method being called if it wants to, and can for example validate if it is called or not.

using System;

 

namespace Silversocial.Client.Modules.Tests.Stubs

{

    public class LoginViewStub : ViewStubBase, ILoginView

    {

        public Action LoginUnsuccessful = delegate { };

 

        void ILoginView.LoginUnsuccessful()

        {

            this.LoginUnsuccessful();

        }

    }

}


The other views are just empty classes for now. The first:

namespace Silversocial.Client.Modules.Tests.Stubs

{

    public class FriendListStub : ViewStubBase, IFriendListView

    {

    }

}


And the other...

namespace Silversocial.Client.Modules.Tests.Stubs

{

    public class RegisterStub : ViewStubBase, IRegisterView

    {

    }

}


The test fixture



Finally it's time to implement the test fixture, just using the standard NUnit syntax for defining the tests and setup method. The SetUp creates all the stubs, so they can be used by each test.

Note I am using the "AAA" pattern which was recently coined by Ayende for RhinoMocks 3.5 - I really like it! This is the way I have written most of my tests in the past, and it's nice to have a formal name for the stages undergone (especially when it's an alliterism)

Here are the tests:

using NUnit.Framework;

using Silversocial.Client.Modules.Tests.Stubs;

using Silverstone;

 

namespace Silversocial.Client.Modules.Tests

{

    [TestFixture]

    public class LoginViewModelTester

    {

        private LoginViewStub loginViewStub;

        private AppShellStub shellStub;

        private FriendListStub friendListStub;

        private RegisterStub registerStub;

        private LoginDataProviderStub dataProviderStub;

        private LoginViewModel viewModel;

 

        [SetUp]

        public void SetUp()

        {

            this.shellStub = new AppShellStub();

            this.friendListStub = new FriendListStub();

            this.registerStub = new RegisterStub();

            this.dataProviderStub = new LoginDataProviderStub();

            this.viewModel = new LoginViewModel(shellStub, dataProviderStub, friendListStub, registerStub);

 

            this.loginViewStub = new LoginViewStub();

            ((IViewModel) this.viewModel).SetView(this.loginViewStub);

        }

 

        [Test]

        public void Login_WithoutUsernameAndPassword_CannotExecute()

        {

            Assert.IsFalse(this.viewModel.Login.CanExecute(null));

        }

 

        [Test]

        public void Login_WithUsernameAndPassword_CanExecute()

        {

            // Arrange

            this.viewModel.User.Username = "abc";

            this.viewModel.User.Password = "abc";

 

            // Assert

            Assert.IsTrue(this.viewModel.Login.CanExecute(null));

        }

 

        [Test]

        public void Login_BeforeCompleted_IsLoggingInIsTrue()

        {

            // Act

            this.viewModel.Login.Execute(null);

 

            // Assert

            Assert.IsTrue(this.viewModel.IsLoggingIn);

        }

 

        [Test]

        public void Login_AfterCompleted_IsLoggingInIsFalse()

        {

            // Act

            this.viewModel.Login.Execute(null);

            this.dataProviderStub.RaiseValidateUserCompleted(new ValidateUserCompletedEventArgs(false));

 

            // Assert

            Assert.IsFalse(this.viewModel.IsLoggingIn);

        }

 

        [Test]

        public void Login_AfterCompleted_RaisesCanExecuteChangedEvent()

        {

            // Arrange

            bool canExecuteChangedCalled = false;

            this.viewModel.Login.CanExecuteChanged +=

                delegate { canExecuteChangedCalled = true; };

 

            // Act

            this.dataProviderStub.RaiseValidateUserCompleted(new ValidateUserCompletedEventArgs(false));

 

            // Assert

            Assert.IsTrue(canExecuteChangedCalled);

        }

 

        [Test]

        public void Login_WhilstLoggingIn_CannotExecute()

        {

            // Arrange

            this.viewModel.User.Username = "abc";

            this.viewModel.User.Password = "abc";

            this.viewModel.IsLoggingIn = true;

 

            // Assert

            Assert.IsFalse(this.viewModel.Login.CanExecute(null));

        }

 

        [Test]

        public void Login_Always_CallsValidateUserOnDataProvider_WithCorrectUser()

        {

            // Arrange

            User userSentToDataProvider = null;

            this.dataProviderStub.ValidateUser +=

                u => userSentToDataProvider = u;

 

            // Act

            this.viewModel.Login.Execute(null);

 

            // Assert

            Assert.AreEqual(this.viewModel.User, userSentToDataProvider);

        }

 

        [Test]

        public void Login_WithInvalidDetails_CallsLoginUnsuccessfulOnView()

        {

            // Arrange

            this.dataProviderStub.ValidateUser +=

                u => this.dataProviderStub.RaiseValidateUserCompleted(new ValidateUserCompletedEventArgs(false));

            bool loginUnsuccessfulCalledOnView = false;

            this.loginViewStub.LoginUnsuccessful +=

                () => loginUnsuccessfulCalledOnView = true;

 

            // Act

            this.viewModel.Login.Execute(null);

 

            // Assert

            Assert.IsTrue(loginUnsuccessfulCalledOnView);

        }

 

        [Test]

        public void Login_WithValidDetails_SetsFriendListViewOnShell()

        {

            // Arrange

            this.dataProviderStub.ValidateUser +=

                u => this.dataProviderStub.RaiseValidateUserCompleted(new ValidateUserCompletedEventArgs(true));

 

            // Act

            this.viewModel.Login.Execute(null);

 

            // Assert

            Assert.AreEqual(this.friendListStub, this.shellStub.View);

        }

 

        [Test]

        public void Login_WithValidDetails_SetsUserOnShell()

        {

            // Arrange

            this.dataProviderStub.ValidateUser +=

                u => this.dataProviderStub.RaiseValidateUserCompleted(new ValidateUserCompletedEventArgs(true));

 

            // Act

            this.viewModel.Login.Execute(null);

 

            // Assert

            Assert.AreEqual(this.viewModel.User, this.shellStub.User);

        }

 

        [Test]

        public void Register_Always_SetsRegisterViewOnShell()

        {

            // Act

            this.viewModel.Register.Execute(null);

 

            // Assert

            Assert.AreEqual(this.registerStub, this.shellStub.View);           

        }

    }

}


And here you can see them running in the Resharper test runner - they all pass!



Now I can confidently pass my ViewModel to my designer to build a view against...

Or can I? Without a data provider, he's going to have problems creating and testing his view. This will be the subject of the next post.

Sunday, 18 May 2008

MVC Architecting Silverlight Applications Part 2 - Building a ViewModel

In Part 1 of this series I talked about and described the Model-View-ViewModel (MVVM) pattern.

In this post I will describe the application I wish to build to demonstrate this pattern to you, and at the same time demonstrate usage of the Silverstone framework.

A "social networking" application


In picking my sample app I wanted to choose something fairly simple. I decided on a small social networking app, which would basically allow the following use cases:
  1. Register - A user can enter their email address and choose a password. The user must confirm their password. Password must be between 4 and 10 characters. After registering, the user proceeds to use case 3

  2. Login - An existing user can enter their email address and password to login. If they enter the wrong details the system should inform them that their details were incorrect. After logging in, the user proceeds to use case 3

  3. View Friends - Displays a list of friends for the currently logged in user.
And that's it! Despite being fairly minimal (and not that useful without being able to add friends or contact them!) that's all I am going to build for now I think.

Creating a solution


We will name this application "Silversocial" (not very imaginative I know, but it's only a demo app!)

You will require Visual Studio 2008, the Silverlight Tools for Visual Studio, and a copy of Silverstone.dll.

Start off by creating a new project of type Silverlight Application named Silversocial.Client.Views. Choose to create a new solution and call it Silversocial



Choose to add a new Web to the solution for hosting the Silverlight content, and call it Silversocial_WebHost (I choose a web application because I prefer them). The solution will be created containing a Silverlight project and a Web application project.



Out of interest, if you right click on the Web Application project properties, you will see a new tab called "Silverlight links" and you will see that the Silverlight application you just created has been added for you automatically. All this really means is that when you build the Silverlight application, the compiled .xap file will be copied into the Web application's /ClientBin folder.



The shell


The shell in a Silverstone application is the central view component which is responsible for managing the other views in the application. Hence, the IShell interface contains just one method:

void SetView(IView view)

Typically an application will implement the shell as the main UI component containing any navigation and common elements for every page (much like ASP.NET's master page). The application will create its own derived IShell interface and expose any elements required by the individual pages through that interface.

So, given the requirements specified earlier, our application's shell only requires an extra property - the currently logged in User. This would allow the shell's view to display the user and allow the other pages to retrieve it when necessary.

To begin, we will create another Silverlight class project called Silversocial.Client.Modules to house the rest of our application's interfaces and concrete implementations. Obviously in a larger application you would consider splitting your code into multiple assemblies grouped by role.

First, the User class which stores the username and password:

using System.ComponentModel;

 

namespace Silversocial.Client.Modules

{

    public class User : INotifyPropertyChanged

    {

        private string username;

        private string password;

 

        public event PropertyChangedEventHandler PropertyChanged = (s,p) => {};

 

        public string Username

        {

            get { return this.username; }

            set

            {

                this.username = value;

                this.PropertyChanged(this, new PropertyChangedEventArgs("Username"));

            }

        }

 

        public string Password

        {

            get { return this.password; }

            set

            {

                this.password = value;

                this.PropertyChanged(this, new PropertyChangedEventArgs("Password"));

            }

        }

    }

}


And now the shell interface itself:

using Silverstone;

 

namespace Silversocial.Client.Modules

{

    public interface IAppShell : IShell

    {

        User User { get; set; }

    }

}


Creating the first ViewModel


For now, we will not worry about building a concrete View. Instead, we will start with our ViewModel. The page we will build is the Login page, and we will create a new class called LoginViewModel for it.

ILoginView will be the interface which our View will have to implement, and has a single method as follows:

using Silverstone;

 

namespace Silversocial.Client.ViewModels

{

    public interface ILoginView : IView

    {

        void LoginUnsuccessful();

    }

}


The contract states that the LoginUnsuccessful() method will be called by the ViewModel if the login is unsuccessful.

On the other side of the equation, our ViewModel will require some way of actually validating the user. In the real world application we will expose a service for doing this, but for now, let's stub it out with the following interface:

using System;

 

namespace Silversocial.Client.Modules

{

    public class ValidateUserCompletedEventArgs : EventArgs

    {

        public ValidateUserCompletedEventArgs(bool successful)

        {

            this.Successful = successful;

        }

 

        public bool Successful { get; set; }

    }

 

    public interface ILoginDataProvider

    {

        event EventHandler<ValidateUserCompletedEventArgs> ValidateUserCompleted;

        void ValidateUser(User user);

    }

}


(Note that this interface is implementing an asynchronous pattern - raising the ValidateUserCompleted event when the method has completed. This ties in well with the fact that Ajax requests are asynchronous, and that Silverlight's WCF ChannelFactory implementation only supports asynchronous requests.)

The View Model is shown below. The main features are:
  • All dependencies passed through as interfaces to the constructor. This includes other views, the data provider, and the shell.

  • Property exposed for the User, which the View can bind to.

  • Property exposed for whether we are in the middle of a login attempt, which the View can bind to.

  • Exposes a Login command which will attempt to use the data provider to validate the user. If successful the callback will set the user on the shell and change to the FriendListView. If unsuccessful the callback will call the LoginUnsuccessful() method on the view.

  • Exposes a Register command which will simply change views to the RegisterView.

using Silverstone;

 

namespace Silversocial.Client.Modules

{

    /// <summary>

    ///    ViewModel for the login page

    /// </summary>

    public class LoginViewModel : ViewModelBase<ILoginView>

    {

        // Dependencies

        private readonly IAppShell shell;

        private readonly ILoginDataProvider dataProvider;

        private readonly IFriendListView friendListView; // This is just an empty view interface for now

        private readonly IRegisterView registerView; // This is just an empty view interface for now

 

        // Commands

        private readonly ICommand login;

        private readonly ICommand register;

 

        // Data Fields

        private readonly User user = new User();

        private bool isLoggingIn;

 

        public LoginViewModel(IAppShell shell, ILoginDataProvider dataProvider, IFriendListView gameChooserView, IRegisterView registerView)

        {

            this.shell = shell;

            this.registerView = registerView;

            this.friendListView = gameChooserView;

            this.dataProvider = dataProvider;

            this.login = new LoginCommand(this);

            this.register = new RegisterCommand(this);

        }

 

        /// <summary>

        ///    Gets the user being logged in

        /// </summary>

        public User User

        {

            get { return this.user; }

        }

 

        /// <summary>

        ///    Gets or sets whether the user is currently being logged in

        /// </summary>

        public bool IsLoggingIn

        {

            get { return isLoggingIn; }

            set

            {

                isLoggingIn = value;

                this.RaisePropertyChanged("IsLoggingIn");

            }

        }

 

        /// <summary>

        ///    Gets the command used to login the user

        /// </summary>

        public ICommand Login

        {

            get { return this.login; }

        }

 

        /// <summary>

        ///    Gets the command used to register a new user

        /// </summary>

        public ICommand Register

        {

            get { return register; }

        }

 

        private class LoginCommand : CommandBase<LoginViewModel>

        {

            public LoginCommand(LoginViewModel viewModel) : base(viewModel)

            {

                this.ViewModel.dataProvider.ValidateUserCompleted += this.HandleValidateUserCompleted;

            }

 

            public override bool CanExecute(object parameter)

            {

                // Used to inform the command framework that we can only login once the user

                // has entered a username and password and we are not already in the middle of a login

                return !string.IsNullOrEmpty(this.ViewModel.User.Username)

                    && !string.IsNullOrEmpty(this.ViewModel.User.Password)

                    && !this.ViewModel.IsLoggingIn;

            }

 

            public override void Execute(object parameter)

            {

                // Call the Async method on the data provider to validate the user.  The ViewModel will

                // handle the response to this method

                this.ViewModel.dataProvider.ValidateUser(this.ViewModel.User);

 

                // Record that we are currently in the middle of a login attempt

                this.ViewModel.IsLoggingIn = true;

            }

 

            private void HandleValidateUserCompleted(object sender, ValidateUserCompletedEventArgs e)

            {

                // Record that we have finished attempting to log in

                this.ViewModel.IsLoggingIn = false;

 

                if (e.Successful)

                {

                    this.ViewModel.shell.User = this.ViewModel.User;

                    this.ViewModel.shell.SetView(this.ViewModel.friendListView);

                }

                else

                {

                    this.ViewModel.View.LoginUnsuccessful();

                }

 

                // Raise the "CanExecuteChanged" method

                this.RaiseCanExecuteChanged();

            }

 

        }

 

        private class RegisterCommand : CommandBase<LoginViewModel>

        {

            public RegisterCommand(LoginViewModel viewModel) : base(viewModel)

            {

            }

 

            public override void Execute(object parameter)

            {

                // Simply use the shell to navigate to the register view when this command is executed

                this.ViewModel.shell.SetView(this.ViewModel.registerView);

            }

        }

    }

}

The next post

In the next post I will talk about unit testing the view model, and will start to wire things together so our designer can build a View for all this.

Tuesday, 13 May 2008

TestDriven.NET adds NUnit support for running Silverlight tests

This is great news from Jamie Cansdale - Silverlight NUnit Projects

He has managed to get a plugin working for NUnit which will run tests compiled for Silverlight directly!

I was previously using the "in-browser" testing framework provided by Jeff Wilcox. As great as this is, it's always nice to be able to run tests directly from the IDE, and this will finally provide it for Silverlight.

So... it looks like I'm going to have to update my Silverlight samples (which I am in the middle of writing) to use this new plugin. These are taking longer than expected but will definitely be published soon - and hopefully before I have to update them again for SL Beta 2!

Oh - and it even works with the Resharper test runner!

Good work Jamie!

Saturday, 3 May 2008

Silverlight 2 - Creating bindings between FrameworkElements using Silverstone

I spent an evening last week working on some code for my Silverstone framework which would allow you to bind a property on an element in XAML to a property on another element. This is something which you can do very easily in WPF, using the {Binding ElementName=xxx} markup extension, but is not currently possible in the Beta 1 version of Silverlight 2.

The syntax I wanted to achieve would be something like this:

<Silverstone:ElementNameBinding Target="textBlock" TargetPropertyName="Text" Source="textBox" SourcePropertyName="Text" />
Essentially I would create a separate control which would bind the text of a source TextBox to the text of a target TextBlock called within the same UserControl (although obviously this can be used with any arbitrary property). The idea to demonstrate this is that when the user changes the text of the TextBox, the text of a TextBlock on the page would reflect what he is typing.

I got as far as getting the code working, so the text was initially populated into the target TextBlock. I did this by creating a Binding between the properties using a bit of reflection combined with the existing SetBinding() method. However, the binding was not picking up any changes! This is because the BindingExpression implementation in Siverlight does not expect the binding source to be a DependencyProperty but rather it expects to find an implementation of INotifyPropertyChanged.

So I continued, trying to manually find the DependencyProperty which was being sourced and adding a listener to its change event. However there is no way to do this in Silverlight! Thinking in WPF terms, I was expecting to be able to grab hold of the metadata for the dependency property and add a handler through that, or alternatively to instantiate a DependencyPropertyDescriptor and calling the AddValueChanged() method.

Neither of these exist in Silverlight 2 Beta 1, so I started playing around in Reflector and trying to manually attach to it, something along the lines of:

DependencyProperty boundProperty = (DependencyProperty)
    propertyType.GetField(this.PropertyName + "Property").GetValue(target);

PropertyChangedCallback propertyChangedCallback = (PropertyChangedCallback)
    boundProperty.GetType().GetField("_propertyChangedCallback", 
        BindingFlags.NonPublic | BindingFlags.Instance)
    .GetValue(boundProperty);

Unfortunately, I ran into the good old Security exception - turns out all this code I was trying to access is marked with the SecurityCriticalAttribute which means you can't access it through reflection.

I pretty much gave up at this point. Begrudgingly I closed my laptop, kissed my girlfriend goodnight and drifted to sleep whilst trying to think of an alternative...

Well a week has gone by now and I thought I'd write this blog article to describe what I'd tried, in case anyone else wanted to try the same thing. It was only when describing my problem that I realised I had been leading myself completely in the wrong direction with my attempts. I could easily utilise the existing binding framework to do the job for me!

All I would need to do is create a "surrogate property" on the binder, create a two-way binding from the source to the surrogate property, and another one-way binding from the surrogate property to the target. By implementing the INotifyPropertyChanged interface on the binder control, I could notify the target whenever the source had changed it would automatically re-read the value. It worked a treat and was so simple!

I think the old saying "if at first you dont succeed..." is very appropriate here. I would also consider the relevance of the "go home and sleep on it" phrase. How many times do you work on problem for an entire day but to no avail, then go home and when come back in the next morning you have the problem solved even before your morning cup of tea!

Anyway, the class Silverstone.ElementNameBinder has been updated now supports this. It is available as of version 0.1 of Silverstone.dll. Get it here or get the source code from the svn repository. If anyone decides to use it and has any questions, of course please let me know.

Have a great day!

Neil

Friday, 2 May 2008

Silverlight 2 - Configuration Files are not picked up

I just saw this post on Mike Taulty's blog about the MaxMessageReceivedSize config setting not working in Silverlight 2's config file.

Mike's solution was to set the property in code but he wasn't sure why he had to do this.

This is happening because the config file is actually never read by Silverlight. It's generated when you add the Service Reference, but it's never picked up. I don't know why it's created actually, it only serves to confuse people! Maybe it will work in a future version... but if you look in the generated Reference.cs of the ImageServiceClient, you will see that the default constructor of the proxy sets the binding to BasicHttpBinding and sets the appropriate Url, essentially mirroring what is in the config file.

Silverlight currently does not have any support for configuration files (there is no System.Configuration namespace at all!). If you wish to configure your Silverlight components, I could suggest that you just use XAML... alternatively you could pass XML through a hidden form field which the Silverlight application reads. The point is that whatever you do will be completely custom.

Anyway I hope this helps someone out there who is wondering why his config changes aren't working.