Sunday, March 28, 2010

Short form: TDD Kata for MVVM on Windows Phone 7

 
Can you complete the kata in under 60 minutes?

* What is a TDD kata? See the Calculator kata
* This is the text-only version. If you're trying this for the first time, work through the detailed version with code snippets.

BEFORE YOU START (SETUP)
a) Install VS 2010 Express for Win Phone 10, and add the unit testing components. [Details].
b) Create a Windows Phone 7 application (a single project, single solution--required by the unit testing framework).
c) Create a code sub-folder (name it whatever you like.)
d) Within the code folder, create sub-folders for Domain, Repository, ViewModel, ThirdParty, and Tests.Unit.
e) Copy/paste the ButtonService.cs class code (from Details link, above) into the ThirdParty folder to provide a Command dependency property.
f) This project is now configured for wp7 unit testing and ICommand data binding (so you may want to save a copy of it for use as a template.)

Set your watch, and begin the kata:
1) Create a test class, CustomerViewModelTests, and add references to the unit test framework.
2) Create a test setup method, in which you instantiate a FakeCustomerRepository class to the ICustomerRepository instance variable.
3) Test that a (new) Customer domain entity and that ICustomerRepoository are passed as constructor parameters to CustomerViewModel.
4) Test that Customer has FirstName/LastName properties, and that CustomerViewModel has FirstName/LastName properties whose values are equal (hint: they WRAP, not duplicate, the Customer properties).
5) Test that a new method CustomerViewModel.VerifyPropertyName(string propertyName) throws an exception if a property name passed to it doesn't exist.
6) Test that CustomerViewModel is instance of type INotifyPropertyChanged.
7) Test that each time properties of the Customer domain entity are changed
* that the PropertyChangedEvent has been raised,
* that the equivalent view model properties have also changed
8) Refactor (move) the INotifyPropertyChanged interface and related methods to ViewModelBase, and then change CustomerViewModel to extend ViewModelBase. Ensure that all tests are still passing.
9) Create another test class, CustomerSaveCommandTests.cs, and add references to the unit test framework.
10) In CustomerSaveCommand, duplicate the ICustomerRepository instance variable and test setup method from CustomerViewModel.
11) Test that Customer and ICustomerRepoository are passed as constructor parameters to CustomerSaveCommand.
12) Test that CustomerSaveCommand is instance of type ICommand.
13) Reference CustomerSaveCommand as a new property of CustomerViewModel, the SaveCommand property.
14) Return to the CustomerViewModelTests class, and verify that customerViewModel.SaveCommand.CanExecute(null) returns false when the nested Customer FirstName and LastName properties are null or empty, else returns true.
15) Create test for CustomerViewModel.CustomerSaveCommand.Execute() method which verifies that
the nested _customerRepository.SaveCustomer() method had been called. Since you are using a fake instead of a mock, add a boolean property, FakeCustomerRepository.SaveMethodCalled which verifies that the repository Save method has been called.
16) Ensure that all tests are passing.

POST-SCRIPT: Connecting the presentation layer to the view model
17) Switch off the unit testing framework in the MainPage.xaml.cs class constructor (set runUnitTests to false).
18) Go to MainPage.xaml ann add an XML namespace reference to the ViewModel namespace.
19) Add another XML namespace reference to the ThirdParty namespace.
20) In the code-behind (MainPage.xaml.cs), in the constructor, instantiate CustomerViewModel (with a Customer parameter whose FirstName/LastName properties are empty) and assign the view model instance to this.DataContext.
21) In MainPage.xaml, in the lower Grid element, drag 2 TextBox controls and a Button to the Phone GUI design surface.
22) Add two-way binding between each TextBox and the corresponding properties of CustomerViewModel.
23) Add a customer property thirdParty:ButtonService.Command="{Binding Path=SaveCommand}" to the Button (i.e. the path is the name of the CustomerViewModel.SaveCommand property.)
24) Set a breakpoint on FakeCustomerRepository.SaveCustomer()
25) Run the Windows Phone emulator.
26) Add FirstName and LastName values.
27) Verify that when the Button is clicked, that this method is called with a Customer parameter containing the FirstName/LastName values you entered.

You're done!

Tuesday, March 23, 2010

xUnit Frameworks incompatible with Silverlight for Windows Phone 7 (.NET Compact Framework)

When I first started building my blog entry on Building a Windows Phone 7 app with MVVM pattern, using TDD and mock objects, I started out using VS 2010 RC and NUnit, using the project templates from the Silverlight for Windows Phone project. I didn't get far through my tests until I hit testing with ICommand (for MVVM) and hit a show-stoppping error. At that point, I switched over to using Visual Studio 2010 Express for WinPhone and the Silverlight Unit Testing Framework.

Some discussions came up yesterday about what exactly Silverlight for Win Phone 7 (which runs under .NET Compact Framework) would and wouldn't support. Essentially most xUnit frameworks theoretically won't work because there is no support for Reflection.Emit in NETCF.

I wanted to go back and confirm what the error was I had hit with NUnit, so I redid the project in scratch in VS 2010 RC with NUnit and took it as far as it would go. As before, it hit the wall with ICommand. Here is the error that is generated:

Saturday, March 20, 2010

Building a Windows Phone 7 app with MVVM pattern, using TDD and mock objects

 
If you've worked through the long form already, try the short form (instructions only).

*****

Source code for this kata published to github.

The goal of this article is to take you through the process of creating a Windows Phone 7 app using Test-Driven Development, designing each feature of the app through a series of incremental tests. The concept is loosely modeled on the TDD Calculator kata as explained by Roy Osherove, although introducing an entire framework, with as many steps as required in the instructions below, moves outside the boundaries of a properly focused kata to what might more properly be called a tutorial. However, my goal is that these steps could be repeatedly practised, like a kata, to develop a consistent approach to TDD using the Windows Phone 7 unit testing framework.

To be able to test effectively in the presentation layer, we want to use the appropriate pattern. The pattern for XAML frameworks (WPF, Silverlight, and now Windows Phone 7) is the Model-View-ViewModel (MVVM) design pattern, in which the ViewModel is isolated from the presentation layer and therefore testable (much like the controller in the MVC pattern, or presenter in the MVP pattern.) The difference here is that the passing of data back and forth between ViewModel and View is done by the framework itself, using a data binding implementation. (This will get much clearer as you work through the tests.)

The initial tests will build the ViewModel itself, ensuring that it has all the pieces its need (implemented interfaces, constructor parameters, and so on) to perform its role in the MVVM pattern. Items not directly part of the SUT (system under test) will be mocked in.

NOTE Like many presentation layer patterns, we will want to use mock objects to substitute classes that are not directly part of the system under test (in this case, the ViewModel.) However, Windows Phone 7 runs on the .NET Compact Framework, which does not support full mocking frameworks such as Rhino Mocks and Moq. Therefore, in this kata we will create fakes (custom mock classes) to verify the repository queries have been called. As per the terminology used by Gerard Meszaros (see Mocks, Fakes, Stubs and Dummies) perhaps the best term to describe what we are building for our custom mock is a "Test Spy".

So let's get started.

Pre-Requisite: Install Windows Phone 7 Unit Testing Framework
To unit test a Windows Phone 7 application, as pre-requisite you must install and configure the Windows Phone 7-specific version of the Silverlight unit testing framework (in which test results are displayed directly in the Winodws Phone emulator screen.) NUnit and other xUnit testing frameworks are not currently compatible with Silverlight and Windows Phone 7.

To install:
1) Download/install Visual Studio 2010 Express for Windows Phone from here.
2) Download the zip file containing the 2 unsigned DLLs from Jeff Wilcox's website.
3) Unzip the 2 DLLs. Then, right-click each DLL individually and select "Properties".
4) In the Properties window for each DLL, click the "Unblock" button. (DLLs off the net are blocked for safety by default.)
5) Launch Visual Studio 2010 Express for Windows Phone.
6) Create a new solution where the project type is "Windows Phone Application"
7) In the Windows Phone Application project, add References for the 2 DLLs that you have just unblocked to this Windows project:
  • Microsoft.Silverlight.Testing
  • Microsoft.VisualStudio.QualityTools.UnitTesting.Silverlight
8) DON'T create any separate class library projects. For now, the unit testing framework requires that all tests/supporting classes be INSIDE the Windows project.
9) DO create a sub-folder in the Windows project, and give it a name to imply this is the application code (it will display in the namespace path). (examples: "AppCode" or "Support")
10) Finally, for the test framework to recognize your tests, you must add code to the code-behind class for the MainPage XAML file.
11) Open the file MainPage.xaml.cs
12) Add using statements to the Silverlight Unit Testing Framework DLLs you referenced earlier:

using Microsoft.Phone.Controls;
using Microsoft.Silverlight.Testing;

13) FINALLY, add the following lines of code (thanks to Jeff Wilcox for this) at the end of the constructor, just below the SupportedOrientations assignment. Note that we have declared a boolean constant (runUnitTests) which we've assigned to true, and then wrapped the code which initializes the unit testing framework into a conditional block based on this constant. With this constant set to true, the Windows Phone emulator will display the test results from the test runner. Later, when we want to view the phone app itself, we'll be setting this constant to false.

const bool runUnitTests = true;

if (runUnitTests)
{
    Content = UnitTestSystem.CreateTestPage();
    IMobileTestPage imtp = Content as IMobileTestPage;

    if (imtp != null)
    {
        BackKeyPress += (x, xe) => xe.Cancel = imtp.NavigateBack();
    }
}

14) Proceed to the TDD kata.

TDD Kata Setup
Within the application code sub-folder (created in the pre-requisites above), create the following sub-folders to function as namespaces: Domain, Repository, ViewModel, and Tests.Unit.

When completed, your SolutionExplorer should look like this:


Test #1 (ViewModel injects corresponding domain entity)
1) In the Tests.Unit sub-folder, create CustomerViewModelTests.cs class.
2) Add the following using statements:

using Microsoft.Silverlight.Testing.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting;

3) Add the [TestClass] attribute to class. (Silverlight Unit Testing Framework is built on top of the MSTest framework, hence the naming convention for the test attributes is the same.)
4) The first test will requite a customer repository, so declare a private instance variable of non-existent type ICustomerRepository.

private ICustomerRepository _customerRepository;

5) Use the Visual Studio 2010 Generate from Usage feature to create the interface:
  • click into the type name, press Alt-Shift-F10
  • select "Generate new type...'"
  • In the Generate New Type dialog (see above), be sure to change the definition to interface, and then click OK.
  • In SolutionExplorer, select the ICustomerRepository class that has been generated in the Tests.Unit folder and use cut-and-paste to MOVE it into the Repository folder
  • In the Repository folder, double-click to open ICustomerRepository and fix its namespace declaration to be for the Repository folder.
  • Return to the test class; where the type reference is now broken
  • click on the type ICustomerRepository and press Alt-Shift-F10 to add a proper reference to the Repository namespace.
6) Create a test setup method.

[TestInitialize]
public void SetUp()
{
}

7) In the setup method, instantiate the _customerRepository interface as a new class, FakeCustomerRepository. Use Alt-Shft-F10 (Generate By Usage) to create this class. In the class signature, add ICustomerRepository as its implemented interface. Now move the class (cut-and-paste it within SolutionExplorer to move it to the Repository folder. Don't forget to edit its namespace entry to match the Repository.)

[TestInitialize]
public void SetUp()
{
    _customerRepository = new FakeCustomerRepository();
}

8) Create a test for CustomerViewModel, in which _customerRepository and a new instance of Customer are passed as parameters to the constructor. Since Customer is a new class; use Alt-Shft-F10 (Generate By Usage) to create the class, and to initialize FirstName and LastName properties. In Solution Explorer, cut-and-paste to move this class to the Domain sub-folder, then edit its namespace declaration to match.

[TestMethod]
public void Constructor_CustomerAndRepositoryInput_InstantiatesSuccessfully()
{
    var customer = new Customer { FirstName = "June", LastName = "Wong" };
    var sut = new CustomerViewModel(_customerRepository, customer);
    Assert.IsNotNull(sut);
}

NOTE It is common in tests to keep track of which class is the "System Under Test" by naming the class variable "sut". The supplied code snippets will use this convention.

9) You are ready for your first test run using the Windows Phone 7 unit test framework. Right-click on the solution, and click "Deploy Solution". The test should pass, displaying as follows:
NOTE The items listed in the test results can be drilled-down into for more detail. Click the item to drill-down, click the back arrow icon at the bottom of the phone emulator to go back.

Test #2 (ViewModel property values match domain property values)
10) The correct implementation of the MVVM pattern is the ViewModel "wraps" the model (i.e the domain.) Therefore a domain entity, Customer, should have it's properties exposed, but not replicated, through the ViewModel. Therefore, create test for CustomerViewModel which verifies that:
    * Customer.FirstName is wrapped by CustomerViewModel.FirstName
    * Customer.LastName is wrapped by CustomerViewModel.LastName

[TestMethod]
public void Constructor_CustomerAndRepositoryInput_CustomerPropertiesEqualViewModelProperties()
{
    var customer = new Customer { FirstName = "June", LastName = "Wong" };
    var sut = new CustomerViewModel(_customerRepository, customer);

    Assert.AreEqual(customer.FirstName, sut.FirstName);
    Assert.AreEqual(customer.LastName, sut.LastName);
}

CustomerViewModel properties

public string FirstName
{
    get
    {
        return _customer.FirstName;
    }
    set
    {
        _customer.FirstName = value;
    }
}

public string LastName
{
    get
    {
        return _customer.LastName;
    }
    set
    {
        _customer.LastName = value;
    }
}

Test #3 (ViewModel.VerifyPropertyName(string property))
11) Create test for CustomerViewModel which verifies that the VerifyPropertyName(string property) throws a custom exception if the property is not part of the model.

[TestMethod]
[ExpectedException(typeof(VerifyPropertyNameException))]
public void VerifyPropertyNameMethod_NonExistentPropertyString_ThrowsException()
{
    var customer = new Customer() { FirstName = "June", LastName = "Smith" };
    var sut = new CustomerViewModel(_customerRepository, customer);
    sut.VerifyPropertyName("NonExistentPropertyName");
}

corresponding method

public void VerifyPropertyName(string propertyName)
{
    foreach(var propertyInfo in this.GetType().GetProperties())
    {
        if(propertyName == propertyInfo.Name)
        {
            return;
        }
    }
    throw new VerifyPropertyNameException();
}

corresponding method, refactored to LINQ

public void VerifyPropertyName(string propertyName)
{
    if (this.GetType().GetProperties().Any(propertyInfo => propertyInfo.Name.Equals(propertyName)))
    {
        return;
    }
    throw new VerifyPropertyNameException();
}

Test #4 (ViewModel implements INotifyPropertyChanged)
12) Create test for CustomerViewModel which verifies that CustomerViewModel is (of type) INotifyPropertyChanged.

[TestMethod]
public void Constructor_CustomerInput_ImplementsINotifyPropertyChanged()
{
    var customer = new Customer() { FirstName = "June", LastName = "Smith" };
    var customerViewModel = new CustomerViewModel(customer);
    Assert.IsInstanceOfType(customerViewModel, typeof(INotifyPropertyChanged));
}

Test #5 (ViewModel.PropertyChanged Event fires when ViewModel property setters called)
13) Create test for CustomerViewModel which verifies that after FirstName/LastName properties have been changed, that the PropertyChanged event has fired.

[TestMethod]
public void ClassProperties_WhenSet_PropertyChangedEventFires()
{
    var customer = new Customer();
    var sut = new CustomerViewModel(_customerRepository, customer);
    var receivedEvents = new List();
    sut.PropertyChanged += ((sender, e) => receivedEvents.Add(e.PropertyName));

    sut.FirstName = "Sabrina";
    Assert.AreEqual(1, receivedEvents.Count);
    Assert.AreEqual("FirstName", receivedEvents[0]);
    sut.LastName = "Moore";
    Assert.AreEqual(2, receivedEvents.Count);
    Assert.AreEqual("LastName", receivedEvents[1]);
}

event raising implementation

protected virtual void OnPropertyChanged(string propertyName)
{
    this.VerifyPropertyName(propertyName);

    var handler = this.PropertyChanged;
    if (handler == null) return;
    var e = new PropertyChangedEventArgs(propertyName);
    handler(this, e);
}

example of call to OnPropertyChanged within a property setter

public string FirstName
{
    get
    {
        return _customer.FirstName;
    }
    set
    {
        _customer.FirstName = value;
        OnPropertyChanged("FirstName");
    }
}

Test #6 (Domain entity properties stay in sync when ViewModel property setters called)
14) Create test for CustomerViewModel which verifies that after FirstName/LastName properties have been changed, that the Customer domain entity properties still match (i.e. have stayed in sync.).

[TestMethod]
public void DomainProperties_ReassignedValues_ViewModelPropertiesTrackChanges()
{
    var customer = new Customer() { FirstName = "June", LastName = "Smith" };
    var sut = new CustomerViewModel(_customerRepository, customer);

    Assert.AreEqual(customer.FirstName, sut.FirstName);
    Assert.AreEqual(customer.LastName, sut.LastName);

    customer.FirstName = "New First Name";
    customer.LastName = "New Last Name";

    Assert.AreEqual(customer.FirstName, sut.FirstName);
    Assert.AreEqual(customer.LastName, sut.LastName);
}

15) Refactoring: Move the interface, the PropertyChanged event, and all related methods into a superclass, ViewModelBase. Ensure that all tests are still passing.

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public void VerifyPropertyName(string propertyName)
    {
        foreach(var propertyInfo in this.GetType().GetProperties())
        {
            if(propertyName == propertyInfo.Name)
            {
                return;
            }
        }
        throw new VerifyPropertyNameException();
    }

    public virtual event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        this.VerifyPropertyName(propertyName);

        var handler = this.PropertyChanged;
        if (handler == null) return;
        var e = new PropertyChangedEventArgs(propertyName);
        handler(this, e);
    }
}

With all 6 tests passing, the Windows Phone emulator should display the tests as follows:

You are now ready to add event-handling to the ViewModel, by way of command classes.
Test #7 (CustomerSaveCommand implements ICommand)
16) Create CustomerSaveCommandTests.cs class
17) Add the following using statements:

using Microsoft.Silverlight.Testing.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting;

18) You will need the same FakeCustomerRepository instance for your tests, so copy the instance variable and the test setup method from the CustomerViewModelTests class.

private ICustomerRepository _customerRepository;

[TestInitialize]
public void SetUp()
{
    _customerRepository = new FakeCustomerRepository();
}

19) Create test for CustomerSaveCommand which passes the _customerRepository instance, and a new Customer instance, as parameters to the constructor, then verifies that CustomerSaveCommand is (of type) ICommand.

[TestMethod]
public void Constructor_CustomerRepositoryInput_ImplementsICommand()
{
    var customer = new Customer() { FirstName = "June", LastName = "Smith" };
    var sut = new CustomerSaveCommand(_customerRepository, customer);
    Assert.IsInstanceOfType(sut, typeof(ICommand));
}

Test #8 (CustomerViewModel.CustomerSaveCommand.CanExecute returns true when Customer is valid, false when Customer is not)
You will now attach CustomerSaveCommand as a property of the CustomerViewModel class, so the remaining tests will be written against CustomerViewModel.
20) Return to the CustomerViewModelTests.cs class.
21) Create a test for the CustomerViewModel.CustomerSaveCommand property which verifies that sut.CustomerSaveCommand.CanExecute(object param) is true.

[TestMethod]
public void CustomerSaveCommandPropertyCanExecute_ValidCustomer_ReturnsTrue()
{
    var customer = new Customer() { FirstName = "June", LastName = "Smith" };
    var sut = new CustomerViewModel(_customerRepository, customer);

    Assert.IsTrue(sut.CustomerSaveCommand.CanExecute(null));
}

22) Refactor the CustomerSaveCommand class so that the condition under which CanExecute(object param) is true is that the properties of the _customer instance all have saveable values (they aren't null or empty.)

public bool CanExecute(object parameter)
{
    return !string.IsNullOrEmpty(_customer.FirstName)
        && !string.IsNullOrEmpty(_customer.LastName);
}

23) Now test the reverse, where passing a Customer instance with empty FirstName/LastName properties to CustomerSaveCommand.CanExecute(object parameter) returns false.

[TestMethod]
public void CustomerSaveCommandPropertyCanExecute_InvalidCustomer_ReturnsTrue()
{
    var customer = new Customer() { FirstName = "", LastName = "" };
    var sut = new CustomerViewModel(_customerRepository, customer);

    Assert.IsFalse(sut.CustomerSaveCommand.CanExecute(null));
}

Test #9 (CustomerViewModel.CustomerSaveCommand.Execute saves the Customer record)
20) Create test for CustomerViewModel.CustomerSaveCommand.Execute() method which verifies that the _customerRepository.SaveCustomer() method had been called.
NOTE Since we can't use a mocking framework such as RhinoMocks in .NET Compact Framework, we will add a test-only property to ICustomerRepository called SaveMethodWasCalled; set to false in the repository constructor, and set to true in the ICustomerRepository.SaveCustomer(Customer customer) method.

[TestMethod]
public void CustomerSaveCommandPropertyExecute_ValidCustomer_RepositoryValidatesCallOccured()
{
    var customer = new Customer() { FirstName = "June", LastName = "Smith" };
    var sut = new CustomerViewModel(_customerRepository, customer);

    sut.CustomerSaveCommand.Execute(null);
    Assert.IsTrue(_customerRepository.SaveCommandCalled);
}

CustomerSaveCommand.Execute() code

public void Execute(object parameter)
{
    if (this.CanExecute(null))
    {
        _customerRepository.SaveCustomer(_customer);
    }
}

FakeCustomerRepository.SaveCustomer() method code

public void SaveCustomer(Customer customer)
{
    // call to not-yet-created WCF client web method
    _saveCommandCalled = true;
}

22) In Solution Explorer, right-click on the solution and choose "Deploy Solution". All 10 tests should be passing. Click on either test class in the Win Phone 7 emulator window to view the complete list of tests.
With all tests passing, you are ready to proceed to the final step - hooking up the ViewModel to the XAML presentation layer.

[Postscript - Building the XAML presentation layer mapped to ViewModel]
23) In Solution Explorer, double-click MainPage.xaml to open it. Adjust the split view between the design and XAML views so that you can easily edit the XAML.
24) In the <phoneNavigation> element, after the xmlns:x attribute, type xmlns:vm= and Intellisense will appear with a list of optional namespaces containing the viewmodel. Press the down arrow to select the ViewModel namespace (as shown below), and then press Enter.
25) Your viewmodel namespace reference should now look like this:

25) Next, you must associate the DataContext of MainPage.xaml to the specific view model class for this page, which is CustomerViewModel.cs. This can be achieved declaratively in the <UserControl.Resources> element of the XAML, but only when your view model has a parameterless constructor. For CustomerViewModel, we will need to associate the DataContext in the code-behind class. To do this, go to the first test you created in CustomerViewModelTests, and copy the instantiation code. Paste it at the end of the MainPage constructor. You'll need to use Alt-Shft-F10 to resolve various references to your classes, and add an instantiation to FakeCustomerRepository (This could be switched out for a "real" repository class later, eg. one built as a wrapper for a web service proxy addressing your web service layer.). When you are done, the data context assignment code should look like this:

ICustomerRepository customerRepository = new FakeCustomerRepository();
var customer = new Customer();
var customerViewModel = new CustomerViewModel(customerRepository, customer);
this.DataContext = customerViewModel;

26) Drag 2 TextBox controls with labels (i.e. TextBlock controls) and a Button onto the design surface. Apply names and pretty-fication until it looks something like this:

27) Add the following attribute to the corresponding XML element for each TextBox (where Path equals the property name from the ViewModel)
for txtFirstName control

Text="{Binding Path=FirstName, Mode=TwoWay}"

for txtLastName control

Text="{Binding Path=LastName, Mode=TwoWay}"

28) You are now ready to add binding to the "SaveCustomer" button.
NOTE At this point, in WPF, the Button's Command property could be set to the data binding for the SaveCustomerCommand class. However, the Command property doesn't exist in Silverlight. This issue has been addressed in a blog entry by Patrick Cauldwell. In the steps below, his ButtonService class is used to provide support for a custom Command binding attribute.

29) Download Patrick Cauldwell's code zip (located at bottom of his blog entry.)
30) Extract the code zip, and locate the ButtonService.cs class.
31) Add a new folder/namespace to your existing folders, named "ThirdParty". Drag and drop ButtonService.cs into this folder.
32) Open the ButtonService.cs class, and change the namespace to match the path of your folder.
NOTE 2010Mar27 As of this writing, there is a TYPO in the ButtonService.cs class. In the initial static registration of the CommandParameterProperty, the 2nd parameter (see code snippet below) is set to typeof(string). This will cause a null error later, in the event raising method, when attempting to cast the command instance to ICommand (because it incorrectly being passed as string.) Therefore, CHANGE the 2nd parameter to be typeof(ICommand).

public static readonly DependencyProperty CommandParameterProperty =
        DependencyProperty.RegisterAttached("CommandParameter", typeof(string), typeof(ButtonService),
        new PropertyMetadata(OnCommandParameterChanged));

33) In MainPage.xaml, you need to add an alias to this namespace (similar to the xmlns:vm namespace you created earlier.) eg. "thirdParty". Add this directly below the xmlns:vm namespace reference.

xmlns:thirdParty="clr-namespace:CustomerApp.Support.ThirdParty"

34) Add a new attribute to the <Button> element to provide data binding to the CustomerSaveCommand.
NOTE 2010Mar27 Path must be bound to the ViewModel's property name, NOT the property type (if they are different; for example, if the property were CustomerViewModel.Save of property type CustomerSaveCommand, then the correct binding path would be "Save").

thirdParty:ButtonService.Command="{Binding Path=CustomerSaveCommand}"

35) You now need to turn off your tests to run your application. BEFORE you do that, run your tests one more time and make sure that all tests are passing.
36) With all tests passing, let's run the application now instead of the tests. To do this, go back to the MainPage.xaml.cs and switch the boolean constant (runUnitTests) to false:

const bool runUnitTests = false;

37) In the FakeCustomerRepository.cs, set a breakpoint on the line of code within the SaveCustomer() method.
38) Finally, launch the application. Enter values into the FirstName and LastName fields, and press the SaveButton.
49) Observe that the breakpoint is hit, the Customer is being passed correctly to the Customer repository's Save command, and the value of the Customer FirstName and LastName properties match the values you entered into the TextBox controls in the XAML layer.

Your Win Phone 7 application using MVVM and unit tests is now complete.

Friday, March 19, 2010

Steps to run the Windows Phone 7 Unit Test Framework successfully

Thanks to assistance from Jeff Wilcox of Microsoft, I now have the Windows Phone 7 Unit Test Framework running successfully!

Here are the steps to get it running:

1) Download/install Visual Studio 2010 Express for Windows Phone from here.
2) Download the zip file containing the 2 unsigned DLLs from Jeff Wilcox's website.
3) Unzip the 2 DLLs. Then, right-click each DLL individually and select "Properties".
4) In the Properties window for each DLL, click the "Unblock" button. (DLLs off the net are blocked for safety by default.)
5) Launch Visual Studio 2010 Express for Windows Phone and create a new Windows Phone project.
6) Add References to the 2 DLLs that you have just unblocked to this Windows project:
* Microsoft.Silverlight.Testing
* Microsoft.VisualStudio.QualityTools.UnitTesting.Silverlight
8) DON'T create any separate class libraries. For now, the unit testing framework requires that all tests/supporting classes be INSIDE the Windows project.
9) DO create a sub-folder in the Windows project to hold code (eg. "CodeLibrary").
10) Create a sub-folder within "CodeLibrary", and name it "Tests".
11) Add a class to the Tests folder, named something like "CustomerViewModelTests.cs"
12) In the class, add the following using statements:

using Microsoft.Silverlight.Testing.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting;

13) Add the [TestClass] attribute above the class declaration.
14) Create a test method with the [TestMethod] attribute, something like this:

[TestMethod]
public void Constructor_CustomerInput_IsHappy1()
{
    Assert.IsTrue(true, "It worked!");
}

15) Finally, for the test framework to recognize your tests, you must add code to the code-behind class for the MainPage XAML file.
16) Open the file MainPage.xaml.cs
17) Add the same 2 using statements that you added to the test class:

using Microsoft.Phone.Controls;
using Microsoft.Silverlight.Testing;

18) Add the following lines of code (thanks to Jeff Wilcox for this) at the end of the constructor, just below the SupportedOrientations assignment

const bool runUnitTests = true;

if (runUnitTests)
{
    Content = UnitTestSystem.CreateTestPage();
    IMobileTestPage imtp = Content as IMobileTestPage;

    if (imtp != null)
    {
        BackKeyPress += (x, xe) => xe.Cancel = imtp.NavigateBack();
    }
}

19) Create another folder within the "CodeLibrary" sub-folder named "ThirdParty".
20) Within this folder, create a class called "ButtonService.cs" and paste in the following code. This code comes from Patrick Cauldwell's blog entry here; it adds support for binding ICommand classes to controls via a custom attribute, Command. The Command attribute is a standard feature in WPF and later versions of Silverlight, but not available by default in the Windows Phone 7 implementation of Silverlight:

public static class ButtonService
{
    // Modified from Patrick Cauldwell's original ButtonService class
    // here: http://www.cauldwell.net/patrick/blog/MVVMBindingToCommandsInSilverlight.aspx

    private static readonly DependencyProperty _commandProperty;

    static ButtonService()
    {
         _commandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(ButtonService),
            new PropertyMetadata(OnCommandChanged));
    }

    public static ICommand GetCommand(DependencyObject dependencyObject)
    {
        return (ICommand)dependencyObject.GetValue(_commandProperty);
    }

    public static void SetCommand(DependencyObject dependencyObject, ICommand value)
    {
        dependencyObject.SetValue(_commandProperty, value);
    }

    private static void OnCommandChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dpceArgs)
    {
        if (dependencyObject is Button)
        {
            string parameter = dependencyObject.GetValue(_commandProperty).ToString();
            Button button = (Button)dependencyObject;
            ICommand command = (ICommand)dpceArgs.NewValue;
            button.Click += delegate(object sender, RoutedEventArgs arg) { command.Execute(parameter); };
        }
    }
}

20) Your environment is now configured! Proceed to the tutorial: Building a Windows Phone 7 app with MVVM pattern, using TDD and mock objects.

Tuesday, March 16, 2010

Saturday, March 13, 2010

TDD kata for ASP.NET MVC controllers (part 2)

The purpose of this kata is to get practice in working with each ActionResult type that is returned from the controller. As before, the controller is tested in isolation (you do not create an actual ASP.NET MVC project, just a class library containing controllers and references to System.Web.Mvc).

In part 1, you set up the test project and related projects, and worked with the following return types derived from ActionResult:
1) ViewResult with ViewData
2) ViewResult with ViewData.Model
3) The same, with a mocked repository
4) RedirectToRouteResult

In part 2, we work with the following additional return types derived from ActionResult:
* RedirectToRouteResult, with TempData
* PlainText (just a string)
* ContentResult (as plain text)
* ContentResult (as RSS feed)
* JsonResult
* JavaScriptResult

If you haven't already completed part 1 of this kata, you can go there now by clicking here.

With part 1 completed, you should have a solution with 4 projects (Tests.Unit, Controllers, Domain, and Repository.) Your tests and controllers projects should have references to System.Web.Mvc and System.Web.Mvc.Routing; all of your controllers should inherit from the Controller base class.

You are now ready to proceed forward into part 2. I'll continue the numbering for tests and steps as of end of part 1:

Test #5 (RedirectToRouteResult with TempData)
17) In HelpTopicsController.cs class, modify the existing method call to also assign a TempData message.
18) In the existing test, assert that the controller instance TempData property has an expected key/value pair, for example: sut.TempData["message"] = "My cross-page message."
Test #6 (Plain Text - just a string)
19) Create TextOutputControllerTests.cs class.
20) Create test for TextOutputController.
21) Call TextOutputController.ShowPlainText(string text) and return the string parameter directly (no casting.)
22) Assert that the returned method string matches input parameter string.
Test #7 (Plain Text as ContentResult)
23) Create another test for TextOutputController.
24) Call TextOutputController.ShowContentResultPlainText(string text) and return Content() as ActionResult.
25) In test, cast the method return to ContentResult.
26) Assert that contentResult.ContentType = "text/plain".
27) Assert that contentResult.Content matches the input string.
Test #8 (RSS feed as ContentResult)
28) Create another test for TextOutputController.
29) Create instance of XDocument() named rssFeed.
30) Call TextOutputController.ShowContentResultRSS(XDocument rssFeed) and return Content(XDocument rssFeed, string contentType) as ContentResult.
31) In test, cast the method return to ContentResult.
32) Assert that contentResult.ContentType = "application/rss+xml".
33) Assert that contentResult.Content matches rssFeed.
Test #9 (JsonResult)
34) Create JsonOutputControllerTests.cs class.
35) Create a test for JsonOutputController.
36) Create instance of List (you created Customer in the Domain namespace in part 1.
37) Use object initializers to add FirstName and LastName property values to 2 instances of Customer in this list.
38) Call JsonOutputController.ShowJsonResult(List customers) and return Json(customers) as ActionResult.
39) In test, cast the method return to JsonResult.
39) In test, cast jsonResult.Data to List with variable name: jsonCustomers.
40) Assert that customers[0] is same as jsonCustomers[0]. (To do this, you will need to override Equals() and GetHashCode() on Customers where equality is based on FirstName and LastName properties.)
Test #10 (JavascriptResult)
41) Create JavaScriptOutputControllerTests.cs class.
42) Create a test for JavaScriptOutputController.
43) Create a simple JavaScript and assign to string javaScriptString eg. "alert('Greetings!');"
44) Call JavaScriptOutputController.OutputJavaScript(string javaScriptString) and return JavaScript(javaScriptString) as JavaScriptResult.
45) Assert that javaScriptString and javaScriptResult.Script are equal.

This demonstrates all returned ActionResult types from controllers (other than file/streaming output types) for ASP.NET MVC v1.

Below are code samples for each of the above tests and controllers.

HelpTopicsControllerTests.cs

[TestFixture]
public class HelpTopicsControllerTests
{
    [Test]
    public void GetHelpMethod_TopicInput_ReturnsRedirectRouteValues()
    {
        const string helpTopic = "searching";
        var sut = new HelpTopicsController();
        var redirectToRouteResult = (RedirectToRouteResult)sut.GetHelp(helpTopic);

        Assert.AreEqual("helpTopic", redirectToRouteResult.RouteValues["controller"]);
        Assert.AreEqual("index", redirectToRouteResult.RouteValues["action"]);
        Assert.AreEqual(helpTopic, redirectToRouteResult.RouteValues["helpTopic"]);
    }

    [Test]
    public void GetHelpWithInfoMethod_TopicInput_ReturnsRedirectTempData()
    {
        const string helpTopic = "searching";
        const string additionalMessage = "My cross-page message";
        var sut = new HelpTopicsController();
        var redirectToRouteResult = (RedirectToRouteResult)sut.GetHelpPlusAdditionalMessage(helpTopic, additionalMessage);

        Assert.AreEqual(additionalMessage, sut.TempData["message"]);
    }
}


HelpTopicsController.cs

public class HelpTopicsController : Controller
{
    public RedirectToRouteResult GetHelp(string helpTopic)
    {
        return RedirectToAction("index", "helpTopic", new { helpTopic = helpTopic });
    }

    public RedirectToRouteResult GetHelpPlusAdditionalMessage(string helpTopic, string additionalMesage)
    {
        TempData["message"] = additionalMesage;
        return RedirectToAction("index", "helpTopic", new { helpTopic = helpTopic });
    }
}


TextOutputControllerTests.cs

[TestFixture]
public class TextOutputControllerTests
{
    [Test]
    public void ShowPlainTextMethod_NoInputParameters_ReturnsPlainText()
    {
        var sut = new TextOutputController();
        const string somePlainText = "This is plain text";
        var plainTextOutput = sut.ShowPlainText(somePlainText);

        Assert.AreEqual(somePlainText, plainTextOutput);
    }

    [Test]
    public void ShowContentResultPlainTextMethod_NoInputParameters_ReturnsContentResultAsTextPlain()
    {
        var sut = new TextOutputController();
        const string somePlainText = "This is plain text";
        var contentResult = (ContentResult)sut.ShowContentResultPlainText(somePlainText);

        Assert.AreEqual("text/plain", contentResult.ContentType);
        Assert.AreEqual(somePlainText, contentResult.Content);
    }

    [Test]
    public void ShowContentResultRSSMethod_RSSFeedInputParameters_ReturnsContentResultAsRSS()
    {
        var sut = new TextOutputController();
        var rssFeed = new XDocument();
        var contentResult = (ContentResult)sut.ShowContentResultRSS(rssFeed);

        Assert.AreEqual("application/rss+xml", contentResult.ContentType);
        Assert.AreEqual(rssFeed.ToString(), contentResult.Content);
    }
}


TextOutputController.cs

public class TextOutputController : Controller
{
    public string ShowPlainText(string text)
    {
        return text;
    }
    
    public ActionResult ShowContentResultPlainText(string text)
    {
        return Content(text, "text/plain");
    }

    public ContentResult ShowContentResultRSS(XDocument rssFeed)
    {
        return Content(rssFeed.ToString(), "application/rss+xml");
    }
}


JsonOutputControllerTests.cs

[TestFixture]
public class JsonOutputControllerTests
{
    [Test]
    public void ShowJsonResultMethod_CustomerInputParameters_ReturnsJsonArray()
    {
        var sut = new JsonOutputController();
        var customers = new List
                            {
                                new Customer {FirstName = "June", LastName = "Jones"},
                                new Customer {FirstName = "Gary", LastName = "Li"}
                            };
        var jsonResult = (JsonResult)sut.ShowJsonResult(customers);
        var jsonCustomers = (List)jsonResult.Data;

        Assert.AreEqual(customers[0], jsonCustomers[0]);
        Assert.AreEqual(customers[1], jsonCustomers[1]);
    }
}


Customer.cs

public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public override bool Equals(object obj)
    {
        var other = (Customer) obj;
        return other.FirstName.Equals(this.FirstName)
               && other.LastName.Equals(this.LastName);
    }

    public override int GetHashCode()
    {
        return this.FirstName.GetHashCode() + this.LastName.GetHashCode();
    }
}


JsonOutputController.cs

public class JsonOutputController : Controller
{
    public ActionResult ShowJsonResult(List customers)
    {
        return Json(customers);
    }
}



JavaScriptOutputControllerTests.cs

[TestFixture]
public class JavaScriptOutputControllerTests
{
    [Test]
    public void GreetingsMethod_NoInput_ReturnsJavaScriptResult()
    {
        var sut = new JavaScriptOutputController();
        const string javaScriptString = "alert('Greetings!');";
        var javaScriptResult = sut.OutputJavaScript(javaScriptString);

        Assert.AreEqual(javaScriptString, javaScriptResult.Script);    
    }
}


JavaScriptOutputController.cs

public class JavaScriptOutputController : Controller
{
    public JavaScriptResult OutputJavaScript(string javaScriptString)
    {
        return JavaScript(javaScriptString);
    }
}

Saturday, March 06, 2010

TDD kata for ASP.NET MVC controllers (part 1)

The purpose of this kata is to work with ASP.NET MVC controllers in isolation: a simple class library that references System.Web.Mvc and creates classes that inherit from the Controller base class, and nothing more than that.

By doing this, the goal is to understand the features of the controller in isolation, without working with the rest of the framework. The main variations in the tests with be:
* which ActionResult type is returned
* use of the Model
* use of dependency injection for mock repositories

This blog post takes you through part 1; to continue to part 2, click here.

1) Create a solution with 4 projects:
   * Tests.Unit
   * Controllers
   * Domain
   * Repository
2) Reference System.Web.Mvc in the test and controllers projects
Test #1 (ViewResult with ViewData)
3) Create WelcomeControllerTests.cs class.
4) Create test for WelcomeController. (WelcomeController must inherit from System.Mvc.Controller.)
5) Call WelcomeController.Index() and cast it to ViewResult.
6) Assert that viewResult.ViewData["Message"] equals "Welcome".
Test #2 (ViewResult with ViewData.Model)
7) Create test for WelcomeController.DisplayCustomers(List<Customer> customers) and cast it to (ViewResult)
8) Assert that viewResult.ViewData.Model is same object as customers (List<Customer>).
Test #3 (ViewResult with ViewData.Model using DI mock repository)
9) Create CustomerControllerTests.cs class.
10) Create CustomerController with constructor that requires ICustomerRepository
11) Use mockCustomerRepository to verify that FindAllCustomers() returns customers as List<Customer>   
12) Call CustomerController.DisplayCustomers() and cast to (ViewResult)
13) Assert that viewResult.ViewData.Model and customers are the same (equivalent) object.
Test #4 (RedirectToRouteResult)
14) Create HelpTopicsControllerTests.cs class.
15) Add a reference to System.Web.Routing, in 2 projects: Tests and Controllers.
16) Create test which validates that HelpTopicsController.GetHelp(string helpTopic) does the following:
    a) returns a RedirectToViewResult.
    b) Asserts that redirectToRouteResult.RouteValues for keys "controller", "action", and "helpTopic" return expected results. (see code sample below for specific values.)

WelcomeControllerTests.cs

[TestFixture]
public class WelcomeControllerTests
{
    [Test]
    public void IndexMethod_NoParameters_ReturnsViewResultMessageWelcome()
    {
        var welcomeController = new WelcomeController();
        var viewResult = (ViewResult)welcomeController.Index();

        Assert.AreEqual("Welcome", viewResult.ViewData["Message"]);
    }

    [Test]
    public void DisplayCustomersMethod_CustomersListParameter_ReturnsCustomersAsModel()
    {
        var welcomeController = new WelcomeController();
        var customers = new List<Customer>();
        var viewResult = (ViewResult)welcomeController.DisplayCustomers(customers);

        Assert.AreSame(customers, viewResult.ViewData.Model);
    }
}


WelcomeController.cs

public class WelcomeController : Controller
{
    public ViewResult Index()
    {
        ViewData.Add("Message", "Welcome");
        return View();
    }

    public ViewResult DisplayCustomers(List<Customer> customers)
    {
        return View(customers);
    }
}


CustomerControllerTests.cs

[TestFixture]
public class CustomerControllerTests
{
    private MockRepository _mockRepository;
    private ICustomerRepository _mockCustomerRepository;

    [SetUp]
    public void SetUp()
    {
        _mockRepository = new MockRepository();
        _mockCustomerRepository = _mockRepository.StrictMock();
    }

    [TearDown]
    public void TearDown()
    {
        _mockRepository.ReplayAll();
        _mockRepository.VerifyAll();
    }

    [Test]
    public void FindAllCustomersMethod_NoParams_ReturnsCustomersAsModel()
    {
        var customerController = new CustomerController(_mockCustomerRepository);
        var customers = new List<Customer>();
        Expect.Call(_mockCustomerRepository.FindAll()).Return(customers);

        _mockRepository.ReplayAll();

        var viewResult = (ViewResult) customerController.FindAllCustomers();
        Assert.AreSame(customers, viewResult.ViewData.Model);
    }
}


CustomerController.cs

public class CustomerController : Controller
{
    private readonly ICustomerRepository _customerRepository;

    public CustomerController(ICustomerRepository customerRepository)
    {
        _customerRepository = customerRepository;
    }

    public ViewResult FindAllCustomers()
    {
        var customers = _customerRepository.FindAll();
        return View(customers);
    }
}


HelpTopicsControllerTests.cs

[TestFixture]
public class HelpTopicsControllerTests
{
    [Test]
    public void FindTopicMethod_StringInput_ReturnsRedirectToRouteResult()
    {
        const string helpTopic = "searching";
        var helpTopicsController = new HelpTopicsController();
        var redirectToRouteResult = helpTopicsController.GetHelp(helpTopic);

        Assert.AreEqual("helpTopic", redirectToRouteResult.RouteValues["controller"]);
        Assert.AreEqual("index", redirectToRouteResult.RouteValues["action"]);
        Assert.AreEqual(helpTopic, redirectToRouteResult.RouteValues["helpTopic"]);
    }
}


HelpTopicsController.cs

public class HelpTopicsController : Controller
{
    public RedirectToRouteResult GetHelp(string helpTopic)
    {
        return RedirectToAction("index", "helpTopic", new {helpTopic = helpTopic});
    }
}

Wednesday, March 03, 2010

TDD/Mocks with Model-View-Presenter compared to with ASP.NET MVC

The last couple of months I've been learning mocks with Model-View-Presenter scenarios, and most recently, applying that to the TDD katas. This was always meant to be a precursor to applying the same concepts to ASP.NET MVC.

In the fall, I worked through the SportsStore demo for ASP.NET MVC in [Sandersen], and over my recent 2 weeks holidays I reviewed Sandersen as well as starting to read [Palermo], who also recommends the concept of a "Presentation" layer that represents a flattened view of the Model for simpler implementation within the View tags.

I am now starting to apply that knowledge by creating a TDD kata that starts (as always) with the domain, but then introduces controllers that originate purely within each test. Only then, as their requirement in the test leads to their generation by Resharper, are they refactored out into their own class files, and moved over into the Mvc project where the real Mvc hooks are added into the controller (a using statement for System.Mvc, inheritance from the base Controller class.)

The goal here is have true test by design (and generation by usage) in ASP.NET MVC, just as I did previously with the Model-View-Presenter scenarios.

The main difference I am seeing is that instead of mocking both the repository and the view, that I am mocking the repository only, since the View is managed by the ASP.NET MVC framework. Therefore, I am primarily concerned with asserts against the ViewData.Model, to make sure that it is the expected domain layer (or flattened presentation layer) object.

I'll be releasing a full TDD kata example in the next few days, but for now, here is an example of one of the tests:

InvoiceControllerTests.cs

[Test]
public void FindInvoice_InvoiceIdInput_ReturnsFlattenedInvoice()
{
    const string invoiceNumber = "1234";
    var invoice = new Invoice(invoiceNumber);
    Expect.Call(_mockInvoiceRepository.FindByInvoiceNumber(invoiceNumber)).Return(invoice);

    _mockRepository.ReplayAll();

    var invoiceController = new InvoiceController(_mockInvoiceRepository);
    var viewResult = (ViewResult) invoiceController.FindInvoiceByInvoiceNumber(invoiceNumber);

    var flattenedInvoice = (FlattenedInvoice) viewResult.ViewData.Model;

    Assert.AreSame(invoice, flattenedInvoice.Invoice);
}