Wednesday, June 10, 2015

Page Modeling with MVVM - Component Driven, Testable Development

In a previous post, I looked at MVVM with MVC and how they can be used together in a modern web situation.  With this approach, the View Models are unit testable so that the User Interface can be tested without needing to be created.

So, what happens if you already have a UI?  Or, you want to test the UI before creating it, how do you do said testing?  This is where page modeling comes to the rescue.  First I'll introduce page modeling at a high level, then I'll investigate several common scenarios and how you can use page modeling with MVVM to create an awesome, testable UI.

Page Modeling

The topic of page modeling has been around for quite some time and is popular with Selenium based testing solutions.  I like to describe page modeling as the process of:
  1. Identifying components of a user interface
  2. Describing the observations (properties) and behaviors (methods) of that component
  3. Understanding how more complex components are composed of other components (simple and/or complex).
At its core, page modeling is breaking down a UI into components, each with well defined behaviors (methods) and observations (properties).  Let's look at an example.

Customer Search Control

Let's say I have a Customer Search control that has a
  • First Name Edit
  • Last Name Edit
  • Search Button
  • Results Table
This can be modeled as three components.
  1. Customer State Manager

    The first and last name input boxes would form one component that can store user input about a customer (think: edit screen, new customer screen, search screen all share this component).
    • First Name Edit
    • Last Name Edit
  2. Customer Collection Manager

    The result table provides the user the ability to edit or delete any given customer in a collection.
    • Results Table
  3. Search Manager

    The search component uses the previously mentioned components and adds a search button (component composed of components). This component would use the first and last name inputs to form a search request. When the Search button is pressed, the component would create a search object, call a WebAPI, get the customer results asynchronously, and populate the result table with these results.
    • Customer State Manager
    • Customer Collection Manager
    • Search Button
Each component would have its own Page Model and likely its own View Model.

Luckily, having created a UI before putting into place any sort of testable design pattern does not mean all is lost. In fact, it is very easy to extract page models from any user interface. Page Models are then easily converted into nearly equivalent View Models. The above exercise of looking at a page and breaking it up into logical components is all that is required to create page models.

Creating View Models from Page Models

Let us continue with the above example. We now have Page Models that look something like the following :

// crude examples of page models
public class CustomerState
{
    public HtmlEdit FirstName {get; set;}
    public HtmlEdit LastName {get; set; }
}

public class ResultTable
{
    public IEnumerable<ResultRow> Rows {get; set; }
    public HtmlButton AddCustomer {get; set; }
}

public class ResultRow
{
    public HtmlRow InnerRow {get; set;}
    public HtmlButton DeleteButton{get; set;}
    public HtmlButton EditButton {get; set;}
    public HtmlSpan FirstName {get; set;}
    public HtmlSpan LastName {get; set;}
}

public class SearchControl
{
    public CustomerState SearchCriteria {get; set;}
    public ResultTable Results {get; set;}
    public HtmlButton SearchButton {get; set;}
}


Let's convert this into a Knockout.js style view model using TypeScript.

class CustomerState {
    FirstName: 
KnockoutObservable<string>;
    LastName: KnockoutObservable<string>;
    constructor(firstName?:string, lastName?:string){
        this.FirstName = ko.observable(firstName);
        this.LastName = ko.observable(lastName);
    }
}

class ResultTable {
    Rows: 
KnockoutObservableArray<ResultRow>;
    constructor(rows?:ResultRow[], button?:HtmlButton){
        this.Rows = ko.observableArray(rows);
        if(button){ // binding can go into view or be passed to view model
            button.addEventListener("click", this.AddCustomer, false);
        }
    }

    AddCustomer(customerInitializer?:any){
        // add it, maybe call back to server, maybe not
    }
}

class ResultRow {
    FirstName: string;
    LastName: string;
    constructor(firstName:string, lastName:string, editButton?:HtmlButton, deleteButton?:HtmlButton){
        this.FirstName = firstName;
        this.LastName = lastName;
        if(editButton){
            editButton.addEventListener("click", this.EditCustomer, false);
        }
        if(deleteButton){
            deleteButton.addEventListener("click", this.DeleteCustomer, false);
        }
    }

    EditCustomer(){
        // open an edit dialog
    }

    DeleteCustomer(){
        // delete it
        // some sort of ajax call to web api to delete this customer 

    }
}

class CustomerSearch {
     State: 
CustomerState;
     Results: ResultTable;
     constructor(state?:CustomerState, results?:ResultTable){
         this.State = state || new CustomerState();
         this.Results = result || new ResultTable();
     }

     Search(){
        // look inside this.State, form a request, use ajax to call web api
        // handle success by populating the result table
     }
}


As you can see, the resulting View Models look extremely similar to the Page Models that were created.  Taken further, the Page Models could even have the same methods to encapsulate some behavior of the component.

public class CustomerState
{
    public HtmlEdit FirstName {get; set;}
    public HtmlEdit LastName {get; set;}
    public CustomerState SetFirstName(string firstName)
    {
        this.FirstName.Text = firstName;
        return this;
    }
    public CustomerState SetLastName(string lastName)
    {
        this.LastName.Text = lastName;
        return this;
    }
}

public class ResultTable
{
    public IEnumerable<ResultRow> Rows {get; set; }
    public HtmlButton AddCustomer {get; set; }
    public AddCustomerDialog ClickAddCustomer()
    {
        Mouse.Click(this.AddCustomer);
        return new AddCustomerDialog();
    }
}

public class ResultRow
{
    public HtmlRow InnerRow {get; set;}
    public HtmlButton DeleteButton{get; set;}
    public ResultTable ClickDeleteButton()
    {
       Mouse.Click(this.DeleteButton);
       return this.ParentTable;
    }
    public HtmlButton EditButton {get; set;}
    public EditCustomerDialog ClickEditButton()
    {
       Mouse.Click(this.EditButton);
       return new EditCustomerDialog();
    }
    public HtmlSpan FirstName {get; set;}
    public HtmlSpan LastName {get; set;}
}

public class SearchControl
{
    public CustomerState SearchCriteria {get; set;}
    public ResultTable Results {get; set;}
    public HtmlButton SearchButton {get; set;}
    public SearchControl ClickSearchButton()
    {
       Mouse.Click(this.SearchButton);
       return this;
    }
}


Testing

Regardless of how you design your UI, there is an easy path toward test-ability. Follows are some approaches you can take (in no particular order) to have a testable UI.

Page Models First

If you start by modeling the user interface with page models which describe the behavior of the user interface component (subtly different from view model which knows how to take inputs and translate them to the underlying system, more later), you can create a full suite of GUI tests without any user interface actually being created. Here is what that could look like...

[CodedUITest]
public class CustomerSearchTests
{

   protected BrowserWindow startingWindow;

   [TestInitialize]
   public void TestInitialize()
   {
       startingWindow = BrowserWindow.Launch("http://mysite.com");
       // manipulate the ui until we reach a search screen
   }

   [CodedUITest]
   public void WhenSupplyingAFirstNameFilterAndSearching_ThenAllResultsContainTheFirstNameCriteriaInFirstNameResults()
   {
      SearchControl search = new SearchControl(startingWindow);
      search.SearchCriteria.SetFirstName("Mike");
      search.ClickSearch();
      Assert.IsTrue(search.Results.Rows.All(x => x.FirstName.Contains("Mike"));
   }
}


This would be a great requirements specification for a developer writing the UI. This model even includes the specific UI element types to use (which may or may not be desirable). As the components are completed, the tests can be run and quick feedback is available to the developer as to whether the controls was created correctly. These page models could be easily converted into view models and the system could be tested via the view models.

View Models First

If you start by modeling the user interaction with your system, you can create a full suite of unit tests without any user interface (graphical or otherwise) being created. Given that the above view models were created in javascript, we would need to use a java script test runner. However, for simplicity, imagine the equivalent view models in C#.

[TestClass]
public class CustomerSearchTests
{

   [TestInitialize]
   public void TestInitialize()
   {
       // nothing to do
   }

   [TestMethod]
   public void WhenSupplyingAFirstNameFilterAndSearching_ThenAllResultsContainTheFirstNameCriteriaInFirstNameResults()
   {
      ISearchControl search = ControlFactory.Create();
      search.SearchCriteria.SetFirstName("Mike");
      search.Search();
      Assert.IsTrue(search.Results.Rows.All(x => x.FirstName.Contains("Mike"));
   }
}


Not much different here that what is above.

UI First

If you create your UI first (or inherit an existing UI), we already looked at how easy it is to extract page models and view models.

Conclusion

Regardless of whether you inherit an existing solution with a not-so-testable UI or you are setting out on a beautiful greenfield project, creating tests for your users interfaces (both graphical and non-graphical) is such a simple task, there really is not much reason not to test. This has the side benefit of tested refactoring that bleeds into the business layer. As you test the UI and move into the interaction between the UI and backing system, it is only natural to continue that journey into the systems that power your UI.

Welcome to the wonderful world of Testing! :)

No comments:

Post a Comment