Friday, September 29, 2017

Unit Testing page with a modal dialog

Writing a unit test for a page that displays a modal dialog requires a little extra work in the code being tested. The problem is that as soon as the dialog box is shown, control passes to it and the unit test hangs. You can create the dialog box, but you must not show it.

Let's create WPF project with a MainWindow that is simply a huge button. Clicking the button opens a Popup window as a modal dialog. The Popup has a text box that the user can enter text into and a text block that shows the lower case version of that text. Our unit test will ensure the text is correctly lowercased.

In Visual Studio (I'm using 2015), start a new WPF application and call it PageTransition.


MainWindow will just be a big button with a RoutedCommand. We already know how to unit test routed commands so I won't go into the details. The XAML looks like this.

<Window x:Class="PageTransition.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:PageTransition"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Window.Resources>
        <RoutedCommand x:Key="OpenCommand"/>
    </Window.Resources>
    <Window.CommandBindings>
        <CommandBinding Command="{StaticResource OpenCommand}" Executed="OpenCommand_Executed"/>
    </Window.CommandBindings>
    <Grid>
        <Button Content="Open" Command="{StaticResource OpenCommand}"/>
    </Grid>
</Window>

MainWindow.xaml.cs is also very simple. Note I made the handle to the popup (p) a class public property. The unit test requires this as we will see later.

using System;
using System.Linq;
using System.Windows;
using System.Windows.Input;

namespace PageTransition
{
    public partial class MainWindow : Window
    {
        public Window p = null;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void OpenCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            p = new Popup();
            p.Show();
        }
    }
}

Now add a window to the project and call it Popup.


The XAML for Popup looks like this. It simply contains a text box, text block, and a close button.

<Window x:Class="PageTransition.Popup"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:PageTransition"
        mc:Ignorable="d"
        Title="Popup" Height="300" Width="300"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Window.Resources>
        <RoutedCommand x:Key="CloseCommand"/>
    </Window.Resources>
    <Window.CommandBindings>
        <CommandBinding Command="{StaticResource CloseCommand}" Executed="CloseCommand_Executed"/>
    </Window.CommandBindings>
    <Grid>
        <StackPanel Orientation="Horizontal" Height="20">
            <TextBox Text="{Binding SomeText, UpdateSourceTrigger=PropertyChanged}" Width="100"/>
            <TextBlock Text="{Binding LowerText}" Width="100"/>
            <Button Content="Close" Command="{StaticResource CloseCommand}"/>
        </StackPanel>
    </Grid>
</Window>

Popup.xaml.cs is also very simple.

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;

namespace PageTransition
{
    public partial class Popup : Window, INotifyPropertyChanged
    {
        private String _SomeText = "";
        private String _LowerText = "";
        public String SomeText
        {
            get { return _SomeText; }
            set { if (_SomeText != value)
                {
                    _SomeText = value;
                    LowerText = _SomeText.ToLower();
                    NotifyPropertyChanged("SomeText");
                }
            }
        }

        public String LowerText
        {
            get { return _LowerText; }
            set
            {
                if (_LowerText != value)
                {
                    _LowerText = value;
                    NotifyPropertyChanged("LowerText");
                }
            }
        }

        public Popup()
        {
            InitializeComponent();
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        private void CloseCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            this.Close();
        }
    }
}

If you run the solution now you can click on the big button to see the popup, then enter some text and make sure it is displayed in lower case. Then you can click the close button.



The unit test has to start MainWindow, simulate a click of the [Open] button, get a reference to the Popup window, simulate the user entering some text, verify the text is displayed correctly, and simulate a click on the [Close] button.

Add a new unit test project to the solution and call it PageTransitionTest. 


Now add references so the project's reference list looks like this.


The Code in UnitTest1.cs looks like this. The line "Popup p = (Popup)mw.p" shows why we save a reference to the popup in the public property p of MainWindow.

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PageTransition;
using System.Windows.Input;
using System.Windows;

namespace PageTransitionTest
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            MainWindow mw = new MainWindow();
            RoutedCommand oc = (RoutedCommand)mw.Resources["OpenCommand"];
            oc.Execute(null, mw);

            Popup p = (Popup)mw.p;
            p.SomeText = "ABC";
            Assert.AreEqual("abc", p.LowerText, "Didn't lower case");

            RoutedCommand cc = (RoutedCommand)p.Resources["CloseCommand"];
            cc.Execute(null, p);
        }
    }
}

If you run the unit test it will succeed. However, if you go back to the MainWindow.OpenCommand_Executed method and change p.Show() to p.ShowDialog() you will find the test hangs because the Popup is now modal. We don't actually need to show the dialog box at all for the unit test. We need to way to know that we are in a unit test and prevent the ShowDialog from executing.

Add the following function to  MainWindow.xaml.cs.

private bool IsInUnitTest()
{
    return AppDomain.CurrentDomain.GetAssemblies().Any((x) => x.FullName.ToLower().Contains("testplatform"));
}

Now change p.ShowDialog() to if (!IsInUnitTest()) p.ShowDialog();

OpenCommand_Executed now looks like this.

private void OpenCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
    p = new Popup();
    if (!IsInUnitTest()) p.ShowDialog();
}

So when opening a dialog box we need to save the window object somewhere that the unit test can find it. Furthermore, if we are opening a modal dialog box we need to not show it if we are running as part of a unit test.

If you run the application you will still see the modal dialog box. If you run the unit test you will not see it but the test will still complete successfully.


Writing a unit test with a RoutedCommand

Continuing from yesterday's post, I want to show you how to write a unit test against a WPF page that uses a RoutedCommand. We will create a page that adds two integers when the user clicks a button. That button will be disabled until the user enters two valid integer addends, and will display their sum when the user clicks the button.

The unit test will validate that the button is disabled when one of the addends is invalid, and also that the correct sum is displayed.

Start a new Visual Studio WPF C# solution. I'm using VS  2015.


The MainPage.xaml will define a command and some controls. It also sets the page's DataContext to the code behind.

<Window x:Class="AddWithCommand.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:AddWithCommand"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Window.Resources>
        <RoutedCommand x:Key="AddCommand"/>
    </Window.Resources>
    <Window.CommandBindings>
        <CommandBinding Command="{StaticResource AddCommand}" CanExecute="AddCommand_CanExecute" Executed="AddCommand_Executed"/>
    </Window.CommandBindings>
    <Grid>
        <StackPanel Orientation="Horizontal" Height="20">
            <TextBox Text="{Binding Addend1, UpdateSourceTrigger=PropertyChanged}" Width="100"/>
            <TextBlock Text=" + "/>
            <TextBox Text="{Binding Addend2, UpdateSourceTrigger=PropertyChanged}" Width="100"/>
            <Button Content=" = " Command="{StaticResource AddCommand}"/>
            <TextBlock Text="{Binding Sum}"/>
        </StackPanel>
    </Grid>
</Window>

The code behind is fairly simple. Replace MainWindow.xaml.cs with this.

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;

namespace AddWithCommand
{
     public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private string _Addend1;
        private string _Addend2;
        private string _Sum;

        public MainWindow()
        {
            InitializeComponent();
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        public String Addend1
        {
            get { return _Addend1; }
            set
            {
                if (_Addend1 != value)
                {
                    _Addend1 = value;
                    NotifyPropertyChanged("Addend1");
                }
            }
        }

        public String Addend2
        {
            get { return _Addend2; }
            set
            {
                if (_Addend2 != value)
                {
                    _Addend2 = value;
                    NotifyPropertyChanged("Addend2");
                }
            }
        }
        public String Sum
        {
            get { return _Sum; }
            set
            {
                if (_Sum != value)
                {
                    _Sum = value;
                    NotifyPropertyChanged("Sum");
                }
            }
        }

        private void CalculateSum(String Addend1, String Addend2)
        {
            try
            {
                Sum = (int.Parse(Addend1) + int.Parse(Addend2)).ToString();
            }
            catch (Exception ex)
            {
                Sum = ex.Message;
            }
        }

        private void AddCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            int t;
            e.CanExecute = (int.TryParse(Addend1, out t) && int.TryParse(Addend2, out t));
        }

        private void AddCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            CalculateSum(Addend1, Addend2);
        }
    }

}

If you run the solution now you will see a page that can add two integers. Yay Computers!



Now let's write our unit test. Add a new unit test project to the solution and call it "AddWithCommandTest".



We will start by adding some references until our reference list looks like this...



Now in the AddWithCommandTest project change UnitTest1.cs to look like this.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using AddWithCommand;
using System.Windows.Input;

namespace AddWithCommandTest
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestAdd()
        {
            MainWindow mw = new AddWithCommand.MainWindow();
            RoutedCommand rc = (RoutedCommand)mw.Resources["AddCommand"];

            mw.Addend1 = "1";
            mw.Addend2 = "X";   // Command should be non-executable
            Assert.IsFalse(rc.CanExecute(null, mw), "AddCommand is executable when addends are invalid");

            mw.Addend2 = "2";   // Command should be executable
            Assert.IsTrue(rc.CanExecute(null, mw), "AddCommand is not executable when addends are valid");

            rc.Execute(null, mw);
            Assert.AreEqual("3", mw.Sum, "Can't even add up!");
        }
    }
}

This defines a test class called TestAdd. It instantiates our test page (which calls the constructor and InitializeComponent) but does not display the page. We can now access everything on the page. We get a reference to the AddCommand and start testing.

Our first test deliberately sets the second addend to an invalid value and verifies the command cannot be executed. The two arguments to CanExecute are as minimal as possible. If your CanExecute logic depends on more information in these arguments you will have to work harder to make the call.

The second test puts a valid value in the second addend and verifies the command can now be executed.

The third test executes the command and ensures the Sum property contains the expected value.

Note that I used the MainWindow reference as the second parameter in each of the calls. Technically they should be called with a reference to the button, but my function doesn't care and I didn't have a reference to the button within easy reach. You cannot use null for this argument because the function will not be called and there will be no error message.

To run your unit test, click on Test in the menu, then Windows -> Test Explorer. Now click "Run All" to see the result. Feel free to break the code in AddWithCommand to see how the test reacts.


Wednesday, September 27, 2017

Unit testing close to the UI

One of the reasons for moving the ViewModel out of the code-behind touted by MVVM evangelists is the ability to perform unit testing. Unfortunately there are costs associated with moving the code that outweigh the benefits. You have to replace RoutedCommands with RelayCommands and the binding of ModelView to View is abstruse. Here is a solution that shows how you can write unit tests for well written MVVM applications without those costs.

Start a new WPF project. I'm in Visual Studio 2015 working in C#.


We will create a simple page that adds two numbers and displays the result or an error. It looks like this...




It will be written with MVVM but the ViewModel will be in the page's code-behind. That means the binding will be intuitive and we can use simple RoutedCommands. Replace the contents of MainWindow.xaml with this...

<Window x:Class="UnitTestAdd.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:UnitTestAdd"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <StackPanel Orientation="Horizontal" Height="20">
        <TextBox Text="{Binding Addend1, UpdateSourceTrigger=PropertyChanged}" Width="100"/>
        <TextBlock Text=" + "/>
        <TextBox Text="{Binding Addend2, UpdateSourceTrigger=PropertyChanged}" Width="100"/>
        <TextBlock Text=" = "/>
        <TextBlock Text="{Binding Sum}" Background="{Binding BackgroundColor}"/>
    </StackPanel>
</Window>

Note that DataContext refers to Self. That allows us to put the ViewModel in the code behind. We will implement INotifyPropertyChanged as all MVVM designs have to. Replace the contents of MainWindow.xaml.cs with this...

using System;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Media;

namespace UnitTestAdd
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private string _Addend1;
        private string _Addend2;
        private string _Sum;
        private Brush _BackgroundColor;

        public String Addend1
        {
            get { return _Addend1; }
            set
            {
                if (_Addend1 != value)
                {
                    _Addend1 = value;
                    NotifyPropertyChanged("Addend1");
                    CalculateSum(_Addend1, _Addend2);
                }
            }
        }
        public String Addend2
        {
            get { return _Addend2; }
            set
            {
                if (_Addend2 != value)
                {
                    _Addend2 = value;
                    NotifyPropertyChanged("Addend2");
                    CalculateSum(_Addend1, _Addend2);
                }
            }
        }
        public String Sum
        {
            get { return _Sum; }
            set
            {
                if (_Sum != value)
                {
                    _Sum = value;
                    NotifyPropertyChanged("Sum");
                }
            }
        }
        public Brush BackgroundColor
        {
            get { return _BackgroundColor; }
            set
            {
                if (_BackgroundColor != value)
                {
                    _BackgroundColor = value;
                    NotifyPropertyChanged("BackgroundColor");
                }
            }
        }
        public MainWindow()
        {
            InitializeComponent();
        }

        private void CalculateSum(String Addend1, String Addend2)
        {
            try
            {
                Sum = (int.Parse(Addend1) + int.Parse(Addend2)).ToString();
                BackgroundColor = new SolidColorBrush(Colors.Transparent);
            }
            catch (Exception ex)
            {
                Sum = ex.Message;
                BackgroundColor = new SolidColorBrush(Colors.Red);
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

    }
}

Whenever the user enters something into either of the TextBoxes we attempt to perform integer addition and display the result. If inputs are missing or invalid we display an error message instead.

Now let's add a unit test. The easiest way to do this is to add another project to the solution. Right click on the Solution in Solution Explorer and select Add New Project. Chose a Unit Test Project and call it "UnitTestAddTest".


You will need to add a reference to the project you are testing plus a few more. Make sure all the references shown below are in your unit test project.


Now write your unit test. Replace the content of UnitTest1.cs with this...

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using UnitTestAdd;

namespace UnitTestAddTest
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestAdd()
        {
            MainWindow mw = new UnitTestAdd.MainWindow();
            mw.Addend1 = "1";
            mw.Addend2 = "1";
            Assert.AreEqual("2", mw.Sum, "Can't even add up!");
        }
    }
}

Because we used MVVM, when the unit test changes properties in MainWindow it's just like the user typing. You can run the unit test by clicking on "Test" in the menu, then Windows and test Explorer. This displays the test explorer page. If you haven't built the solution, do so now.

Click on "Run All" and you should be rewarded with a green check mark.

Now deliberately break the application. Return to MainWindow.xaml.cs and replace the + with * to give you Sum = (int.Parse(Addend1) * int.Parse(Addend2)).ToString();

Run the unit test again and you will see a red X and an error message.


Message boxes and other popups work with this technique too. Undo the change you just made so the code reads like Sum = (int.Parse(Addend1) + int.Parse(Addend2)).ToString(); again and add a line below to display a message box like this...

Sum = (int.Parse(Addend1) + int.Parse(Addend2)).ToString();
BackgroundColor = new SolidColorBrush(Colors.Transparent);
MessageBox.Show("The answer is " + Sum);

When you run the program you see the answer displayed in a message box.


Now try running the unit test again. The message box popups up out of nowhere and the test waits for you to respond before continuing. Sometimes the message box pops up behind Visual Studio so keep an eye on your task bar.





Monday, September 25, 2017

Tracking down errors thrown by the framework

I had a weird problem today. I have a page that allows the users to view change orders. A certain type of change orders displayed errors when an expander was expanded. There is a DataGridComboBoxColumn in the expander that is only displayed for this type of change order. The error message gave no indication of what code was throwing the error. The error message said...

"TextBlock" TargetType does not match type of element "TextBlockComboBox"

I should have picked up on the word "TargetType" but I didn't.

I started by assuming it was a converter issue so I set breakpoints on the Convert method of each of my converters. This is easy to do in Visual Studio 2015.

I hit Ctrl-B to open the new Function Breakpoint dialog box and then entered "Convert" in the Function Name.


Clicking [OK] sets a break point on every method named "Convert" although it does not update the UI so you just have to trust it. I then disabled all breakpoints, moved through the program to the point of failure, enabled all breakpoints and caused the program to fail. None of the breakpoints were hit. OK, so it's not a converter problem.

Then I thought it might be a CanExecute problem. I've noticed that command CanExecute methods are often not called until the commands' controls are visible. I put breakpoints on all those methods and none of them were hit either.

In my past experience any style problems are raised by InitializeComponent but eventually I decided to double-check the styles on the DataGridComboBoxColumn that I suspected was causing the problem. Sure enough I found ElementStyle="{StaticResource EnabledTextBlock}" and it turned out the style was defined as <Style TargetType="{x:Type TextBlock}" x:Key="EnabledTextBlock">. One of my minions had changed the column definition last week and it had not been properly tested.

So the short story is that when the framework raises error messages complaining about TargetType, it's a style problem.

Tuesday, September 12, 2017

Dynamically changing classes in Angular

I have a web page that has a potentially long header. On smaller screens the header wraps easily and can consume too much of the screen space. One option is to truncate the header to one line with an ellipsis. The option I chose to implement was to reduce the font size as the screen gets narrower.

There are many ways this can be done. I chose to implement one of two classes for the header depending on whether the client width was more than or less than 1024 pixels.

Start a new C# ASP.NET project called AdjustPointSize. Chose the empty template and uncheck 'Host in the cloud'.



Right-click the project in the solution explorer and select Add -> HTML Page. Name the new page "MainPage.html".

Replace the contents of MainPage with the following html.

<!DOCTYPE html>
<html ng-app="defaultApp">
<head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script>
        angular.module("defaultApp",[])
            .controller("defaultCtrl", function ($scope, $window) {
                $scope.innerWidth = $window.window.innerWidth;

                angular.element($window).on('resize', function () {
                    $scope.$apply(function () {
                        $scope.innerWidth = $window.window.innerWidth;
                    });
                });
            });
    </script>
    <style>
        .largeHead {font-size: 24pt;}
        .smallHead {font-size: 12pt;}
    </style>
</head>
<body ng-controller="defaultCtrl">
    <div ng-class="(innerWidth < 640)?'smallHead':'largeHead'">Welcome to the Adjust Point Size Demo!</div>
</body>
</html>

You can run this immediately and you will see as you narrow the web page the font will suddenly get smaller.

The two key parts to this demo are the resize handler and the binding of ng-class.

We first use the angular.element function to get an angular window object and attach an event handler to the 'resize' event. This handler saves the window's innerWidth to a $scope variable. The handler is inside a $apply method to force re-evaluation of bindings affected by it. When the window size changes, the value of $scope.innerWidth is updated and anything bound to it is re-evaluated. Now we have to bind something to it.

The binding is in the ng-class definition. We compare the window width to a predefined value and assign one of two predefined classes to the DIV. When you make the window narrower or wider you will see the font change size.

The technique is very useful on mobile platforms where rotating from landscape to portrait will fire the event. It puts another tool in your responsiveness tool box.