In This Chapter
• Creating unit tests with the Silverlight Unit Testing Framework for Windows Phone
• Using the tag expressions editor
• Test metadata and assertions
• Windows Phone Test Driven Development
• Code driven UI tests
• The Microsoft Automation framework
• Asynchronous tests
• Using a custom IoC container
• Testing of trial applications
• Mocking launchers and choosers
Unit testing saves time and helps to find defects early in the development cycle. Unit tests become assets that provide assurance that further development or refactoring has not broken something. The more tests you create, the more confidence you have when adding features and fixing bugs. This is especially true if you are working with a team of developers.
Manual ad hoc testing becomes less effective as an app increases in size and complexity. Having a solid suite of unit tests can actually decrease the time it takes to get your app to the marketplace. This is because often an exorbitant amount of time is spent on ad hoc testing in the last stages of development before a release.
Another benefit of unit tests is that they can act as a tacit form of documentation. There is a distinct lack of code documentation in many software houses today; unfortunately it is all too common. Unit tests can assist a developer in understanding how an app works, and unit tests are less susceptible than traditional system documentation to implementation drift, where design changes and feature creep can see the documentation of an app become outdated. Unit tests tend to fare better because they are verifiable. Relying solely on unit tests to document a system is, however, not wise. Unit tests should be considered production code, requiring their own adequate documentation.
For all its benefits, unit testing does have a cost. It takes time and skill to write effective unit tests. In your career, you may have experienced the reluctance of management to support unit testing because it is invariably seen as time stolen from writing product code. It is that old story: Developers craft a product and then scramble in the last moments to iron out defects, producing fixes that frequently introduce new bugs. Unit testing can help to alleviate that last minute scramble.
Some may also see unit tests as a liability because when there are substantial changes to an app, unit tests have to be rewritten. This cost is, however, usually overstated and can sometimes reflect unsound development practices.
Testing and patterns of testing vary, and divided opinion on the topic has spurred many a heated debate. This chapter is not about affirming one approach over another. The techniques and tools presented in this chapter should, however, make a worthy addition to your development toolbox.
Throughout this book you have seen the use of Model-View-ViewModel (MVVM). MVVM comes into its own when combined with unit testing. In fact, it is one of the key motivations for using the pattern. MVVM allows you to separate UI technology specific code so that it can be tested without the user interface.
This chapter delves into the Silverlight UTF (Unit Testing Framework) for Windows Phone. The chapter begins with a walk-through of the creation of a unit test project from scratch. You see how to create test classes and test methods, and get to know the tag expressions editor, which provides a useful mechanism for selecting which tests are to be run.
The UTF is then explored more deeply by creating a simple chat client app, in which you see how to verify a viewmodel before creating its UI. Following the creation of the view, the test suite is extended to include code driven UI tests, where we manipulate the user interface, simulating button clicks and other user actions from a unit test. The chapter illustrates how to perform asynchronous testing and touches on the Microsoft Automation framework.
Some advanced topics such as Inversion of Control (IoC) and mocking are also discussed, and you learn how to use a custom IoC container.
Finally, we put it all together to see how to perform testing of trial applications and how to hide or reveal content based on a mock licensing service. You then look at a custom API for mocking launchers and choosers.
There is a lot to cover in this chapter, and the tools and techniques presented here can help spot unintended side effects, increase the robustness of code, assist in focusing the development effort, and perhaps save you some time.
By finding and guarding against defects, an automated test can save you many times the cost of creating it over the lifetime of a project. This chapter looks at three kinds of automated testing: unit testing, integration testing, and coded UI testing.
A unit test verifies that a single unit of work, usually a class method, works correctly. Unit tests should generally avoid accessing the file system, database, or network resources. Types requiring this kind of access ideally rely on an abstracted API and have the types used in production substituted with stubs or mock types during testing. Unit tests should execute rapidly and cause no side effects that affect other unit tests.
Integration tests verify that multiple classes function correctly together. Integration occurs after unit testing. It involves testing groups of classes that have been unit tested.
Coded UI tests simulate a user interacting with the application. These tests rely on an automation API to allow programmatic access to UI elements. For example, an automation object can be used to raise a button’s Click
event.
There are other types of tests as well, including acceptance testing, performance testing, and stress testing. You often use different tools or extensions to your testing framework for these types of tests. Despite some tests not being formally unit tests, it’s fine to use the existing unit testing framework and tools to perform other kinds of testing. The unit testing tools presented in this chapter are a good starting point for many other types of tests.
It is important to partition your tests by test type. For example, unit tests should execute quickly. By grouping integration tests with unit tests, you risk slowing the execution of the group, which may make running the tests tedious, resulting in avoidance of unit testing altogether by you or another developer. Other test types, such as system tests, which test the entirety of your app, may rely on resources that are unavailable during a unit testing session. By including a system test with your unit tests, you may inadvertently prevent the test suite from passing.
The API of the Silverlight UTF for Windows Phone is the same as the Silverlight UTF for the browser. In fact, it is almost a superset of the Microsoft desktop CLR UTF. This cross-screen compatibility means that you can run all your existing unit tests just as they are.
Unfortunately tooling support for the Silverlight UTF for Windows Phone is nonexistent. Unlike the Microsoft desktop UTF, there is no Visual Studio integration whatsoever. This means you cannot run individual unit tests from Visual Studio as you might with a desktop application. Windows Phone test projects are Silverlight for Windows Phone applications. Therefore, do not bother trying to create a new unit test from the Test menu in Visual Studio, as it is unsupported for Windows Phone.
While the tooling support is absent, all of the metadata and assertions, discussed later in this chapter, are identical for both the desktop CLR and Silverlight for Windows Phone, and creating a unit test project is easy, as you see in the following section.
Download the Silverlight UTF for Windows Phone from Microsoft’s Jeff Wilcox’s blog at http://bit.ly/krk41Q. These assemblies are also present in the downloadable sample code, but to be sure you have the latest, download them from Jeff’s blog.
The download contains two assemblies: Microsoft.Silverlight.Testing.dll and Microsoft.VisualStudio.QualityTools.UnitTesting.Silverlight.dll.
As stated previously, a unit test project is a bona fide Windows Phone application. To create a test project, create a new Windows Phone Application project by using the Add New Project dialog (see Figure 22.1).
Once the test project has been created, add a reference to the two assemblies.
If you are working in a team and using a source control system, I recommend checking-in all non-FCL assemblies, regardless of whether an installer exists to place the assemblies in a known location. This allows you to propagate updated assemblies easily and helps prevent issues around incorrect installer versions.
Some changes need to be made in the new project’s MainPage.xaml.cs. The OnNavigatedTo
method override should disable the system tray so that it does not intrude on the UTF test harness. A test page can then be created using the Microsoft.Silverlight.Testing.UnitTestSystem.CreateTestPage method (see Listing 22.1).
The app’s root visual is assigned to the test page instance. Windows Phone does not allow the assignment of the RootVisual
in App.xaml.cs. This is why the code to create the test page is placed in the MainPage
class and not in the App
class.
The BackKeyPress
method is overridden so that pressing the hardware button causes the IMobileTestPage
object’s NavigateBack
method to be called. If the NavigateBack
method returns false, such as when the first test page is displayed, then the navigation is cancelled, preventing navigation.
public partial class MainPage
{
public MainPage()
{
InitializeComponent();
}
IMobileTestPage mobileTestPage;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
SystemTray.IsVisible = false;
UnitTestSettings settings = UnitTestSystem.CreateDefaultSettings();
/* To set the TagExpression use the following: */
// UnitTestSettings settings = UnitTestSystem.CreateDefaultSettings();
// settings.TagExpression = "UnitTest";
/* Add test assemblies as shown: */
// settings.TestAssemblies.Add(typeof(Class1).Assembly);
// settings.TestAssemblies.Add(typeof(TestClass2).Assembly);
UIElement testPage = UnitTestSystem.CreateTestPage();
Application.Current.RootVisual = testPage;
Application.Current.Host.Settings.EnableFrameRateCounter = false;
mobileTestPage = testPage as IMobileTestPage;
}
protected override void OnBackKeyPress(CancelEventArgs e)
{
if (mobileTestPage != null)
{
e.Cancel = mobileTestPage.NavigateBack();
}
base.OnBackKeyPress(e);
}
}
To create a test class, add a new class to your test project. Then add the following two using statements to the top of the class:
using Microsoft.Silverlight.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
Test related attributes and assertion types can be found within the namespace Microsoft.VisualStudio.TestTools.UnitTesting. Even if you have not used the Visual Studio UTF before, it is easy to pick up since the attributes are self-descriptive.
Tests consist of test classes and test methods. The unit test framework relies on metadata in the form of attributes for identifying unit test classes and methods at runtime. To indicate that a class is a unit test class, decorate it with the TestClassAttribute
, as shown in the following example:
[TestClass]
public class FirstTest : SilverlightTest
{
[TestMethod]
[Description("A first unit test.")]
public void ShouldAlwaysPass()
{
Assert.IsTrue(true);
}
}
To run the test, set the test project as the startup project by right-clicking on the project node in the Visual Studio Solution Explorer and selecting Set as Startup Project. Then debug the solution by selecting Start Debugging from the Debug menu or by pressing F5. When the app starts, the tag expressions editor is presented (see Figure 22.2).
The tag expressions editor allows you to run all or a subset of the tests. It has a countdown timer that automatically commences testing after 5 seconds.
When the test completes, a review page is presented in the test harness (see Figure 22.3).
The test harness allows you to drill down to see the details of each test method (see Figure 22.4). The content of the test method’s Description
attribute is presented beneath the test method’s name.
Tests run on a single thread and are executed within the Silverlight sandbox.
When you have a large number of tests, you need some way to execute just one, or a subset, of the tests. The UTF includes a tagging language, which allows you to create tag expressions that specify which tests should be run during a unit testing session.
Every test class and test method has an implicit set of tags, shown in the following list:
• Type or method name, for example, TestClass1, TestMethod1.
• Full type or method name, for example, TestClass1.TestMethod1.
• The priority specified by a Priority
attribute if present. For further information on tag expressions and the Priority
attribute, see the section “Priority
Attribute” later in the chapter.
You can also explicitly assign a test class or test method with a tag using the Tag
attribute. The Tag
attribute accepts a single string parameter, which is used to associate a test with a group of tests.
The following is an example of a test method decorated with a Tag
attribute:
[TestMethod]
[Tag("UITest")]
public void AlwaysPass()
{
Assert.IsTrue(true, "Test method intended to always pass.");
}
Multiple Tag
attributes can be applied to any test class or method in your test suite. Tags can also be useful in selecting different kinds of tests to run.
When the testing session begins, you have the opportunity to enter a tag expression. To run the test method from the previous excerpt, or any other test methods with a matching attribute, you could enter UITest
as the tag expression.
The tag expression syntax provides a set of operators to control test selection. The !
(not) operator, for example, allows you to prevent those tests with a particular tag from being executed. A tag expression of !UITest
causes all tests that do not have the tag UITest
to be executed. A tag of All-(UITest+IntegrationTest)
causes all tests that do not have the UITest
or IntegrationTest
tag to execute.
The tag expression syntax uses Extended Backus-Naur Form (EBNF). You can use the symbols presented in Table 22.1 in tag expressions.
To set the tag expression without using the UTF tag expressions editor, use the TagExpression
property of the UnitTestSettings
class, as shown in the following example:
void OnLoaded(object sender, RoutedEventArgs e)
{
SystemTray.IsVisible = false;
UnitTestSettings settings = UnitTestSystem.CreateDefaultSettings();
/* To set the TagExpression use the following: */
UnitTestSettings settings = UnitTestSystem.CreateDefaultSettings();
settings.TagExpression = "UnitTest";
UIElement testPage = UnitTestSystem.CreateTestPage(settings);
Application.Current.RootVisual = testPage;
Application.Current.Host.Settings.EnableFrameRateCounter = false;
mobileTestPage = testPage as IMobileTestPage;
}
As you have seen, attributes are used to identify test classes and methods. Attributes can also be used to provide other metadata information to the UTF. This section examines each of the UTF attributes in greater detail and provides descriptions of their usage, purpose, and effect on test execution.
The TestClass
attribute indicates that a class contains test methods.
The TestMethod
attribute indicates that a method should be included in the suite of tests. The Ignore
attribute can be used to temporarily exclude a test method from the suite of tests. Methods decorated with the TestMethod
attribute should be public and have a void return type.
The UTF provides attributes that allow you to specify methods that should be run before and after test execution. You can provide methods that are executed once for each test assembly, once for each test class, and once for each test method. The order in which methods decorated with each particular attribute are run is presented in Figure 22.5.
The AssemblyInitialize
attribute identifies a method that contains code to be used before all tests in an assembly are run and to allocate resources obtained by the assembly. A method decorated with the AssemblyInitialize
attribute must be public and static, and have a void
return type. The following demonstrates the use of the AssemblyInitialize
attribute:
[AssemblyInitialize]
public static void AssemblyInitialize()
{
/* Assembly initialization logic goes here. */
}
The test framework runs a method that is marked with the AssemblyInitialize
attribute only if that method is a member of a class that is marked with the TestClass
attribute.
The AssemblyCleanup
attribute is analogous to the AssemblyInitialize
attribute but occurs at the end of the test run. As with the AssemblyInitialize
attribute, a method decorated with this attribute should be located in a test class. The following shows an example of a method decorated with the AssemblyCleanup
attribute:
[AssemblyCleanup]
public static void AssemblyCleanup()
{
/* Assembly cleanup logic goes here. */
}
The ClassInitialize
attribute provides the opportunity to run code before any of the tests in the test class have run and to allocate resources to be used by the test class.
A method decorated with the ClassInitialize
attribute must be public and static with a void
return type. Only one method in a class may be decorated with this attribute.
The following shows an example of a method decorated with the ClassInitialize
attribute:
[ClassInitialize]
public static void ClassInitialize()
{
/* Class initialization logic goes here. */
}
The ClassCleanup
attribute is analogous to the ClassInitialize
attribute but occurs after all test methods have completed within the test class. The following shows an example of a method decorated with the ClassCleanup
attribute:
[ClassCleanup]
public static void ClassCleanup()
{
/* Class cleanup logic goes here. */
}
The TestInitialize
attribute is used to indicate that a method decorated with this attribute should be called before every test method within a test class. A method decorated with the TestInitialize
attribute must be public and have a void return type. The following shows an example of a method decorated with the TestInitialize
attribute:
[TestInitialize]
public void TestInitialize()
{
/* Test initialization logic goes here. */
}
If more than one method is decorated with the TestInitialize
attribute in a test class, it prevents the execution of all test methods. Furthermore, it does so silently. If you find that the debugger is failing to hit a break point in a test method, look for a duplication of the TestInitialize
attribute.
The TestCleanup
attribute is useful for resetting the state of shared resources in between test methods, such as an object that is used by all tests within a test class.
[TestCleanup]
public void TestCleanup()
{
/* Test cleanup logic goes here. */
}
The following attributes allow you to control various other aspects of test execution.
The TestProperty
attribute allows arbitrary metadata to be associated with a test method. For example, you could use it to store the name of a test pass that this test covers by decorating the test method with [TestProperty("TestPass", "Accessibility")]
. Unlike the Silverlight UTF for the browser and the Visual Studio desktop CLR unit testing tools, the Windows Phone UTF does not display TestProperty
information within the test harness.
The Ignore
attribute can be used to temporarily exclude a specific test from execution. This can be useful for excluding a test that is blocking other tests from running. It allows you to retain compilation of the test, rather than merely commenting out the code.
The Description
attribute is used on test methods and allows you to provide a string describing the purpose and/or behavior of the test method. The description is then presented beneath the title of the test result details screen on the phone (refer to Figure 22.4). The following example demonstrates the use of the Description
attribute:
[TestMethod]
[Description("An example test demonstrating the Description attribute.")]
public void ShouldAlwaysPass()
{
Assert.IsTrue(true);
}
The Timeout
attribute allows you to specify an amount in milliseconds in which a test method must complete or the test method fails.
The following is an example of using the Timeout
attribute to prevent an asynchronous test method from taking longer than 100 milliseconds to execute:
[TestMethod]
[Asynchronous]
[Timeout(100)]
public void ShouldFailDueToAsyncTestTimeout()
{
EnqueueDelay(1000);
EnqueueTestComplete();
}
This test method fails because the call to EnqueueDelay
delays the completion of the test by 1000 ms (1 second), and the Timeout
attribute specifies that the test should take no longer than 100 ms. The EnqueueDelay
attribute and the other asynchronous related attributes are discussed later in this chapter.
The Owner
attribute is used to specify the person responsible for maintaining, running, and/or debugging the test. This attribute accepts a single string parameter, indicating the name of the owner, as shown in the following example:
[TestMethod]
[Owner("Daniel Vaughan")]
public void ShouldAlwaysPass()
{
Assert.IsTrue(true);
}
The value of the attribute is not displayed within the test harness of the current Windows Phone UTF.
Verifying that your code produces a correct response to a known set of values is one thing; verifying that it responds appropriately when given bad input is another. This is called negative testing, and it allows you to verify that code behaves correctly even when exceptional conditions arise.
Ordinarily, if a test method raises an exception, the exception causes that test to fail. In a negative test, however, raising an exception may be the expected behavior. In that case, the ExpectedException
attribute can be used to indicate that if an exception of a particular type, with a particular message, is not thrown, then the test should fail. The following example demonstrates the use of the ExpectedException
attribute:
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void ShouldFailDueToArgumentException()
{
throw new ArgumentException("Invalid argument supplied.");
}
The TagAttribute
, AsynchronousAttribute
, and BugAttribute
are not available in the Microsoft desktop CLR test framework. Using any of them, therefore, means that your unit tests will not be compatible with the desktop CLR.
The Asynchronous
attribute informs the UTF that a test method should be considered to be in a running state until the SilverlightTest.EnqueueTestComplete
method is called.
Use the Asynchronous
attribute in combination with the Timeout
attribute to prevent the test method from taking forever if it fails to call EnqueueTestComplete
.
For more information on the Asynchronous
attribute, see the section “Asynchronous Testing.”
The Bug
attribute allows you to associate a known bug with a unit test. Its key characteristic is that it reverses the result of a test, so you can first use it to demonstrate the existence of a bug, and once the bug is resolved, the Fixed
property indicates that the test should pass. For example, if a method is able to reproduce a bug in the software, it may first look like this:
[Bug("TFS 123")]
[TestMethod]
public void ExerciseBuggyCode()
{
Assert.IsTrue(false); /* Simulates some broken code. */
}
Consider the preceding test method. It passes because the Bug
attribute’s Fixed
property is false by default.
When the issue is resolved, you can add the Fixed
property to the Bug
attribute, which removes the inversion behavior of the Bug
attribute.
[Bug("TFS 123", Fixed = true)]
[TestMethod]
public void ExerciseBuggyCode()
{
Assert.IsTrue(true); /* Simulates issue resolved. */
}
Contrary to first assumptions, the Priority
attribute is not used by the test system. Its purpose can be defined by you. It is, however, added to the set of implicit expression tags. The attribute requires an integer constructor argument, which specifies the priority value. For example:
[TestMethod]
[Priority(1)]
public void AlwaysPass()
{
Assert.IsTrue(true, "Test method intended to always pass.");
}
We can then use the tag expression Priority1
to execute this test method.
UTF test classes normally inherit from the SilverlightTest
class. While inheriting from this class is not strictly required, it does provide for some advanced features such as asynchronous testing.
Asynchronous unit test methods are a key feature of the UTF for Silverlight, and one that is notably absent from the Microsoft UTF for the desktop CLR.
By inheriting from SilverlightTest
, you lose compatibility with the Desktop CLR Visual Studio unit test framework. Therefore, do not inherit from this class if you want to retain cross-screen compatibility and do not require advanced UI testing facilities, such as asynchronous tests.
Assertions are the cornerstone of UTF. The Assert
class has a multitude of test related method overloads that allow you to ensure the validity of your app’s state and behavior. The following is the core set of assertions used by most test classes:
• AreEqual and AreNotEqual—These methods rely on the Object.Equals
method to determine object equality. There are various overloads for primitives, as well as reference types.
• AreSame and AreNotSame—Tests for whether two variables refer to the same object instance. These methods rely on the Object.ReferenceEquals
method. There are various overloads for primitives, as well as reference types.
• Fail—Allows you to explicitly fail a test based on logic within the test method.
• Inconclusive—Allows you to explicitly set the outcome of a test to inconclusive.
• IsTrue and IsFalse—Verifies that a Boolean value is either true
or false
.
• IsInstanceOfType and IsNotInstanceOfType—Verifies that an object instance does or does not inherit from a specified type.
• IsNull and IsNotNull—Verifies that an object is, or is not, null
.
If an Assert
method fails, it raises a UnitTestAssertException
, which is handled by the UTF infrastructure and reported back to you as a test failure.
To complement the set methods provided by the Assert
classes, there exists a CollectionAssert
class with methods particular to collections, and a StringAssert
class, which provides assertions based on regular expressions.
The following is the list of collection assertions that enable you to verify the contents of collections:
• AllItemsAreInstancesOfType—Verifies that all items in a collection are, or inherit from, a specified type.
• AllItemsAreNotNull—Verifies that no item in the collection is null.
• AllItemsAreUnique—Verifies that a collection is a set; each item occurs only once.
• AreEqual and AreNotEqual—Verifies that two collections have the same number of items, and that each item in the first collection is equal to the item at the same index in the second collection.
• AreEquivalent and AreNotEquivalent—Verifies that two collections have the same number of items and that each item in the first collection has an item that is equal to it in the second collection. This differs from AreEqual
and AreNotEqual
in that order does not matter.
• Contains and DoesNotContain—Verifies that a collection contains a specified item.
• IsSubsetOf and IsNotSubsetOf—Verifies that all items in one collection exist in another specified collection.
The StringAssert
class provides various methods for verifying the contents of strings:
• Contains—Verifies that a string contains a specified substring.
• Matches and DoesNotMatch—Uses a regular expression to verify that the specified string matches, or does not match, a specified pattern.
• StartsWith and EndsWith—Verifies that a string starts or ends with a specified string.
Sometimes you may like to execute your tests without having to interact with the UTF interface. It can be annoying to have to tap the Use Tag button to kick of the unit tests before the countdown timer reaches zero.
Unfortunately, the test harness does not allow you to hide the tag expressions editor in the current version of the Windows Phone UTF. Nonetheless, it is shown here in anticipation of a future release of the UTF.
To hide the tag expressions editor (in a future release) and to run the tests as soon as the test harness launches, you need to modify two properties in the UnitTestSettings
class; set the StartRunImmediately
property to true, and set the ShowTagExpressionEditor
to false. This is demonstrated in this excerpt from the MainPage.xaml.cs file in the downloadable sample code:
void OnLoaded(object sender, RoutedEventArgs e)
{
SystemTray.IsVisible = false;
UnitTestSettings settings = UnitTestSystem.CreateDefaultSettings();
settings.StartRunImmediately = true;
settings.ShowTagExpressionEditor = false;
UIElement testPage = UnitTestSystem.CreateTestPage(settings);
Application.Current.RootVisual = testPage;
Application.Current.Host.Settings.EnableFrameRateCounter = false;
mobileTestPage = testPage as IMobileTestPage;
}
The current implementation of the UTF for Windows Phone does not support inclusion of tests from assemblies outside the test project; all tests must reside in the Windows Phone test project.
When this shortcoming is rectified, if you want to have a single project for testing and include other test assemblies, add assemblies to the TestAssemblies
collection of the UnitTestSettings
class, as shown:
UnitTestSettings settings = UnitTestSystem.CreateDefaultSettings();
settings.TestAssemblies.Add(typeof(TestClass1).Assembly);
settings.TestAssemblies.Add(typeof(TestClass2).Assembly);
UIElement testPage = UnitTestSystem.CreateTestPage(settings);
Sometimes, you need to test classes and members that are not public but are internal to a project, such as during system testing, where you need to interact with UI elements directly. To have access to internal members, you must allow the test project access to the internal members of the main project. You do this by placing an InternalsVisibleTo
attribute into the AssemblyInfo
class of the main project, as shown:
[assembly: InternalsVisibleTo("WindowsPhone7Unleashed.Tests.Silverlight")]
The InternalsVisibleTo
attribute accepts a single parameter, which is the name of the assembly. If the test project has a strong name, then the full name of the assembly must be used, including its public key token.
Examples of the InternalsVisibleTo
attribute are in the downloadable sample code.
This section explores a simple chat client app. First, a custom chat service that is used to send and receive messages is discussed. Next, you examine how to substitute types using mocking and look at mocking the chat service. You see how to test the functionality of a viewmodel without needing to create a view for it. Finally, you learn how to create coded UI tests using the UTF TestPanel in combination with the Microsoft Automation framework.
The code for this section is located in the ChatClientView
and ChatClientViewModel
classes in the downloadable sample code.
The chat client app relies on a chat service, represented by an IChatService
interface. The IChatService
specifies a method to send a message, presumably to a cloud service, and an event to indicate when the service receives a message. The interface is shown in the following excerpt:
public interface IChatService
{
void SendMessage(string message);
event EventHandler<ChatMessageEventArgs> MessageReceived;
}
A mock chat service is used during unit testing. Mock objects mimic the behavior of real objects in controlled ways. In this case, MockChatService
acts as loopback; when the SendMessage
method is called, the MessageReceived
event is raised, as shown:
public class MockChatService : IChatService
{
public bool MessageSent { get; private set; }
public string LastMessage { get; private set; }
public void SendMessage(string message)
{
MessageSent = true;
LastMessage = message;
OnMessageReceived(new ChatMessageEventArgs(message));
}
public event EventHandler<ChatMessageEventArgs> MessageReceived;
public void OnMessageReceived(ChatMessageEventArgs e)
{
MessageReceived.Raise(this, e);
}
}
The viewmodel for the chat client uses the chat service to send messages using a SendCommand
(see Listing 22.2). When the SendCommand
executes, the IChatService.SendMessage
method receives the message.
The viewmodel subscribes to the IChatService.MessageReceived
event. When a message is received, it is placed into an ObservableCollection
of messages, which are then presented in the view.
public class ChatClientViewModel : ViewModelBase
{
readonly IChatService chatService;
public ChatClientViewModel(IChatService chatService)
{
this.chatService = ArgumentValidator.AssertNotNull(
chatService, "chatService");
sendCommand = new DelegateCommand(
delegate
{
if (string.IsNullOrEmpty(message))
{
return;
}
chatService.SendMessage(message);
Message = string.Empty;
},
delegate { return !string.IsNullOrEmpty(message); });
chatService.MessageReceived
+= (sender, args) => messages.Add(args.Message);
PropertyChanged += delegate { sendCommand.RaiseCanExecuteChanged(); };
/* The rest of the constructor is shown later in the chapter. */
}
readonly ObservableCollection<string> messages
= new ObservableCollection<string>();
public ObservableCollection<string> Messages
{
get
{
return messages;
}
}
string message;
public string Message
{
get
{
return message;
}
set
{
Assign(() => Message, ref message, value);
}
}
readonly DelegateCommand sendCommand;
public ICommand SendCommand
{
get
{
return sendCommand;
}
}
/* The rest of the class is shown later in the chapter. */
}
With the infrastructure in place, you can create some unit tests for the viewmodel.
An alternative approach, known as Test Driven Development (TDD), sees the creation of the unit tests first. If you are willing to take an interface first approach or have a tool such as Resharper that allows you to quickly generate class members, you may choose to create the unit tests before you implement the class that is the subject of the unit tests.
Listing 22.3 shows various test methods for verifying that the viewmodel is able to send a message correctly and that it is able to respond correctly when it receives a message from the chat service.
[TestClass]
public class ChatClientTests : SilverlightTest
{
[TestMethod]
public void ShouldSendMessage()
{
string testMessage = "Hello from unit test.";
MockChatService chatService = new MockChatService();
ChatClientViewModel viewModel = new ChatClientViewModel(chatService);
viewModel.Message = testMessage;
viewModel.SendCommand.Execute(null);
Assert.AreEqual(chatService.LastMessage, testMessage);
}
[TestMethod]
public void ShouldNotSendMessageIfEmpty()
{
MockChatService chatService = new MockChatService();
ChatClientViewModel viewModel = new ChatClientViewModel(chatService);
viewModel.Message = string.Empty;
viewModel.SendCommand.Execute(null);
Assert.IsFalse(chatService.MessageSent);
}
[TestMethod]
public void CommandShouldBeDisabledIfMessageIfEmpty()
{
MockChatService chatService = new MockChatService();
ChatClientViewModel viewModel = new ChatClientViewModel(chatService);
viewModel.Message = string.Empty;
Assert.IsFalse(viewModel.SendCommand.CanExecute(null));
}
[TestMethod]
[Description(@"When the chat service receives a message,
the client displays it.")]
public void CommandShouldBeEnabledIfMessageNotEmpty()
{
MockChatService chatService = new MockChatService();
ChatClientViewModel viewModel = new ChatClientViewModel(chatService);
viewModel.Message = "Test";
Assert.IsTrue(viewModel.SendCommand.CanExecute(null));
}
[TestMethod]
public void ShouldReceiveMessage()
{
string testMessage = "Hello from unit test.";
MockChatService chatService = new MockChatService();
ChatClientViewModel viewModel = new ChatClientViewModel(chatService);
chatService.OnMessageReceived(new ChatMessageEventArgs(testMessage));
CollectionAssert.Contains(viewModel.Messages, testMessage);
}
}
The result of running the unit tests is shown in Figure 22.6.
The view allows the user to send and receive messages via the viewmodel. The view consists of the following elements:
• A TextBox
to allow the user to enter a message to send.
• An AppBar
that includes an AppBarIconButton
to execute the viewmodel’s SendCommand
. For more information on the AppBar
, see Chapter 8, “Taming the Application Bar.”
• A ListBox
to display all incoming messages.
The following excerpt shows the main content from the ChatClientView.xaml
page:
<StackPanel Grid.Row="1"
Style="{StaticResource PageContentPanelStyle}">
<u:AppBar>
<u:AppBarIconButton
Command="{Binding SendCommand}"
Text="Send"
IconUri="/ChatClient/Images/AppBarMessageSend.png"
x:Name="button_Send"
AutomationProperties.AutomationId="button_Send" />
</u:AppBar>
<TextBox x:Name="textBlock_Message"
Text="{Binding Message, Mode=TwoWay,
UpdateSourceTrigger=Explicit}"
TextWrapping="Wrap" AcceptsReturn="True"
u2:UpdateSourceTriggerExtender.UpdateSourceOnTextChanged="True"/>
<TextBlock Text="Messages" Style="{StaticResource LabelTextStyle}"/>
<ListBox x:Name="listBox" ItemsSource="{Binding Messages}" Height="400">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"
Style="{StaticResource NormalTextStyle}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
The various input controls have been named. You see later in this chapter how the naming of elements allows you to directly manipulate them from a coded UI unit test.
If you are not creating coded UI tests, it is better to refrain from naming elements unless you need to refer to them in the code-beside. This helps identify those elements that are referred to in the code-beside, and decreases the verbosity of the XAML.
The view’s code-beside (ChatClientView.xaml.cs
) instantiates the viewmodel and supplies the viewmodel with an instance of the MockChatService
, as shown in the following excerpt:
public ChatClientView()
{
InitializeComponent();
DataContext = new ChatClientViewModel(new MockChatService());
}
Later in the chapter you see how to replace the MockChatService
for a real chat service implementation using Inversion of Control (IoC).
The final result of the chat page is shown in Figure 22.7.
A key benefit of the MVVM pattern is that it allows you to unit test an app without needing to display its UI. Sometimes, however, you may want to verify the behavior of the UI by emulating user interaction. This can be achieved using coded UI tests.
The advantage of coded UI tests is that they provide your app with a test environment that is closer to the real world; they allow you to simulate the tapping of buttons or entering of text while the app is running.
The downside of coded UI tests is that they are tightly coupled to the user interface, which is often the most likely thing to change over time. If the UI is modified, it can mean having to rewrite your coded UI tests.
Coded UI tests can be performed with the UTF by populating the SilverlightTest
object’s TestPanel
with the page or control that you want to test, as demonstrated in the following excerpt:
[TestClass]
public class ChatClientUITests : SilverlightTest
{
ChatClientView view;
[TestInitialize]
public void PreparePage()
{
view = new ChatClientView();
TestPanel.Children.Add(view);
}
...
}
Recall that the TestInitialize
attribute causes the method to be called before each test method in the parent class. You see that when the PreparePage
method is called, it creates a new instance of the ChatClientView
and places it into a container control within the test harness.
This section looks at the three test methods in the ChatClientUITests
class.
The first test method simply verifies that the PhoneApplicationPage
is presented in the TestPanel
, as shown:
[TestMethod]
public void ShouldDisplayDefaultSize()
{
Assert.IsTrue(view.ActualWidth > 0);
}
The ShouldDisplayDefaultSize
method demonstrates that you are able to interact with the UI elements from code. In fact, while the tests are running their actions are viewable within the test harness (see Figure 22.8).
To interact with controls on the page, give the test project access to the internal members of the main project. This is done by placing an InternalsVisibleTo
attribute in the AssemblyInfo
class of the main project, shown in the previous section “Testing Non-Public Members.”
When the InternalsVisibleTo
is placed in the target project, IntelliSense is enabled for controls on the page.
To test that the Send button is enabled when the message TextBox
is populated, set the text and then perform an assert using the button, as shown in the following excerpt:
[TestMethod]
public void ButtonShouldBeEnabled()
{
view.textBlock_Message.Text = "Test";
Assert.IsTrue(view.button_Send.IsEnabled);
}
The UTF is designed to run tests on a single thread. The Silverlight UI thread uses a message pump and often calls to update properties or reactions to property changed events are driven forward using Dispatcher
calls. The result is that a UIElement
may not be in the state that you think it should be when you are performing an assertion. Fortunately, the Silverlight UTF compensates with some built-in asynchronous features.
The Asynchronous
attribute tells the UTF to keep running the test until the test class’s base method, SilverlightTest.EnqueueTestComplete
is called or until an unhandled exception occurs. This allows you to spin off work to other threads or queue work to happen on the UI thread, while keeping the test alive.
The SilverlightTest.EnqueueCallback
method allows actions to be queued on the UI thread. This may include actions such as verifying the value of a UIElement
’s property after a PropertyChanged
event has been given the opportunity to propagate.
The following excerpt demonstrates how to use the Asynchronous
attribute in combination with the EnqueueCallback
method to set the text of a sample view’s message box. It simulates a tap to the Send Message button and then verifies that the ListBox
has been correctly populated:
[TestMethod]
[Asynchronous]
public void ButtonShouldSendMessage()
{
view.textBlock_Message.Text = "Test";
EnqueueCallback(() => ((IAppBarItem)view.button_Send).PerformClick());
EnqueueCallback(() => Assert.IsTrue(view.listBox.Items.Count > 0));
EnqueueTestComplete();
}
When the textBlock_Message.Text
property is set, the Send button is not enabled until the viewmodel’s SendCommand
responds to the Message
property’s PropertyChanged
event. Once it has had a chance to respond, the command enables itself.
The EnqueueCallback
method queues the PerformClick
method, so that by the time it is called, the button is enabled. Similarly, verification that the ListBox
has been populated must occur after the UI has responded to the new item in the Messages
collection in the viewmodel.
The UTF is notified that all nonblocking activity has been completed by calling the base class’s SilverlightTest.EnqueueTestComplete
method.
When performing code driven UI testing, often you need to manipulate elements in ways that are not possible using the API of the elements themselves. The Button
class, for example, does not have a PerformTap
method to raise the Tap
event. The built-in Silverlight controls are generally well encapsulated and are not designed to simulate interaction via code. For this, we turn to the Microsoft UI Automation framework, which consists of a secondary API for manipulating UI elements from code. The Automation framework is designed for accessibility software, allowing third-party software to manipulate the UI on behalf of a user with a disability.
The Automation API is able to manipulate elements, such as a Button
, using internal methods of the various built-in FrameworkElement
types, which are ordinarily off-limits.
In this example, a button is placed on the ChatClientView
page. When tapped, it sets the view’s custom ButtonClicked
property to true. The XAML for the button is as follows:
<Button x:Name="button_AutomationTest"
Content="AutomationTestButton"
Click="button_AutomationTest_Click" />
The code-beside for the view contains the event handler for the Click
event of the button:
public bool ButtonClicked { get; private set; }
void button_AutomationTest_Click(object sender, RoutedEventArgs e)
{
ButtonClicked = true;
}
An AutomationPeer
, located in the System.Windows.Automation.Peers namespace, is used to verify that the button was indeed clicked. The AutomationPeer
object is then used to retrieve an IInvokeProvider
specific to the Button
class, which allows you to invoke the button’s Click
event, shown in the following excerpt:
[TestMethod]
[Asynchronous]
public void DemonstrateButtonClick()
{
AutomationPeer peer
= FrameworkElementAutomationPeer.CreatePeerForElement(
view.button_AutomationTest);
IInvokeProvider provider
= (IInvokeProvider)peer.GetPattern(PatternInterface.Invoke);
provider.Invoke();
EnqueueCallback(() => Assert.IsTrue(view.ButtonClicked));
EnqueueTestComplete();
}
Behind the scenes, the AutomationPeer
is simply calling an internal method of the ButtonBase
named AutomationButtonBaseClick
, which raises the button’s Click
event.
The ButtonAutomationPeer
raises the button’s Click
event and not its Tap
event. Unfortunately, the Tap
event is not supported by the Automation API in the current 7.5 release of the Windows Phone SDK.
Windows Phone presents some interesting challenges for testing apps in various deployment scenarios. For example, a common requirement for phone apps is the need to behave differently depending on whether the app has been purchased or whether it is in trial mode. Furthermore, the launcher and chooser API does not play well with unit tests because launchers and choosers cause the app to be deactivated. Hence, there is a need for some mechanism to alter the behavior of an app, depending on its deployment scenario, and to decouple and replace various phone-specific types so that code can be more easily tested. One way to achieve these things is by using Inversion of Control.
IoC encompasses two key concepts presented in this chapter: service location and dependency injection.
Service location allows you to associate a type, often an interface, with a concrete implementation of that type so that when another component requests the first type, they are automatically delivered an instance of the associated type. This allows your code to be decoupled from any particular concrete implementation, which increases flexibility, and allows the behavior of your app to be modified, without having to change all references to a particular concrete implementation.
Dependency Injection (DI) assists in the creation of objects by automatically supplying known types to the objects constructor during instantiation.
Employing IoC in Windows Phone apps helps overcome the challenges brought on by the rigid infrastructure and numerous sealed classes that account for many of the key types in the SDK and that hinder both unit testing and ad hoc testing.
Numerous IoC frameworks have been designed for Silverlight for the browser and the desktop CLR. Silverlight for Windows Phone, however, is based on the Microsoft .NET Compact Framework (.NET CF) and lacks the ability to generate Microsoft Intermediate Language (MSIL) and, in particular, Reflection.Emit
, a key ingredient in generating types at runtime. This limitation means that none of the well-known IoC projects, such as the Microsoft Patterns and Practices Unity framework, exist for the phone. I have, however, created an IoC container and DI framework, based on work by Ian Randall (http://microioc.codeplex.com/), for use with Windows Phone.
Like much of the code presented in this book, these classes are present in the downloadable sample code. I recommend, however, that you procure the latest code from http://calciumsdk.com, where you are sure to have the most up-to-date version.
The custom Dependency
class is a static class used to associate types or resolve instances of types at runtime. The Dependency
class serves as a proxy to an instance of IoC container, allowing you to change the underlying IoC container implementation. It does this by leveraging the Microsoft.Practices.ServiceLocation API, which provides a container agnostic type resolution mechanism. The Dependency
class also uses a custom IDependencyRegistrar
to create type associations, something notably absent from the Microsoft.Practices.ServiceLocation API.
To use the Dependency
class, an IoC container implementation must be specified.
Initialization of the IoC infrastructure should be performed as soon as possible before the app displays its UI. This helps prevent type resolution failures.
In the sample, located in the WindowsPhone7Unleashed.Examples project, the container initialization code is placed in the App
class, as shown in the following excerpt:
void InitializeContainer()
{
SimpleContainer container = new SimpleContainer();
container.InitializeServiceLocator();
#if DEBUG
Dependency.Register<ILicensingService>(new MockLicensingService
{
IsTrial = true
});
Dependency.Register<IMarketplaceDetailTaskAdapter>(
new MockMarketplaceDetailTaskAdapter(
() => Debug.WriteLine("Launching Marketplace Detail...")));
#else
Dependency.Register<ILicensingService, LicensingService>();
Dependency.Register<IMarketplaceDetailTaskAdapter,
MarketplaceDetailTaskAdapter>();
#endif
}
Once the container is initialized, the Dependency
class is used to register type associations. Creating a different set of type associations, depending on the build configuration, can be achieved using preprocessor directives. When using a DEBUG build configuration, mock implementations of the ILicensingService
interface and the IMarketplaceDetailTaskAdapter
interface are used. These types wrap and substitute the functionality provided by the built-in MarketDetailTask
and LicenseInformation
classes.
Rather than duplicating your type mappings across test and core projects, you may choose instead to create a dedicated class that includes type associations for each scenario you are covering: test, release, trial, and so on.
If you are familiar with unit testing in Silverlight for the browser, you may wonder why there is no coverage of dynamic mocking in this chapter. The reason is that dynamic mocking does not exist in Windows Phone due to, as previously mentioned, the absence of Reflection.Emit.
Providing a trial version of your app may help to improve its monetization. If someone has the opportunity to try out your app, they may be more likely to purchase it (assuming that it is any good). A usual requirement for an app employing a trial version is to provide the user with a link to buy the app within the app itself. In the following demonstration you see how to display an application bar button when an app is in trial mode.
The Microsoft.Phone.MarketPlace.LicenseInformation
class contains a single method called IsTrial()
, which indicates whether your app has been purchased. When a user downloads the trial version of your app from the marketplace, this method returns true. On all other occasions, such as when debugging, this method returns false.
To ensure that an app functions correctly in both scenarios, you abstract the LicenseInformation
class.
The downloadable sample code includes a custom ILicenseService
interface, which contains a single property called Trial
, indicating the trial state of the app. There are two implementations of this interface: one for testing and one for production.
The production implementation named LicensingService
wraps a LicenseInformation
instance, as shown in the following excerpt:
public class LicensingService : ILicensingService
{
public bool Trial
{
get
{
LicenseInformation licenseInformation
= new LicenseInformation();
return licenseInformation.IsTrial();
}
}
}
During development and testing, the LicensingService
class is replaced by a MockLicensingService
class, which allows you to change the value of its Trial
property. See the following excerpt:
public class MockLicensingService : ILicensingService
{
bool trial = true;
public bool Trial
{
get
{
return trial;
}
set
{
trial = value;
}
}
}
The particular implementation that is used depends on the type association within the IoC container. When the project is built using a release configuration, then LicensingService
is used; otherwise the MockLicensingService
is used.
Within the ChatClientView
page, there is a button whose visibility depends on a viewmodel property called BuyOptionVisible
. See the following excerpt:
bool buyOptionVisible;
public bool BuyOptionVisible
{
get
{
return buyOptionVisible;
}
private set
{
Assign(() => BuyOptionVisible, ref buyOptionVisible, value);
}
}
In the viewmodel constructor the value of the buyOptionVisible
field is determined using the ILicensingService
, which is resolved via the static Dependency
method called Resolve
:
var licensingService = Dependency.Resolve<ILicensingService>();
buyOptionVisible = licensingService.IsTrial;
An AppBarMenuItem
is bound to the BuyOptionVisible
property, as shown:
<u:AppBar.MenuItems>
<u:AppBarMenuItem Text="Buy"
Command="{Binding BuyCommand}"
Visibility="{Binding BuyOptionVisible,
Converter={StaticResource BooleanToVisibilityConverter}}" />
</u:AppBar.MenuItems>
A value converter is used to convert the Boolean value to a Visibility
enum value.
When the user taps the button, the BuyCommand
is executed. In the next section you see how the BuyCommand
uses an abstracted MarketPlaceDetailTask
to launch the built-in marketplace application on the phone.
Unit testing code that uses a launcher or chooser directly is a challenge because both types of tasks cause your app to be deactivated. No means to abstract launchers or choosers exists out of the box, therefore I have included in the downloadable sample code a set of classes that do just that.
Just as we abstracted the built-in LicenseInformation
class in the previous section, here we do the same with the MarketDetailTask
. The MarketDetailTask
is a launcher which, when shown, takes the user to the built-in marketplace application. When showing the MarketplaceDetailTask
during a debugging session, the native marketplace application displays an error because it expects an app with an id that has been officially published to the marketplace.
The main issue, however, is that when the MarketplaceDetailTask
is shown, it deactivates the app. Thus, it makes sense to abstract the task as in the LicenseInformation
class in the previous section.
The abstracted custom interface for the MarketplaceDetailTask
is named IMarketDetailTask
and contains a single method named Show
. There are two implementations of this interface. The first, named MarketplaceDetailTaskAdapter
, calls the Show
method of a built-in MarketplaceDetailTask
instance when its own Show
method is called, as shown in the following excerpt:
public class MarketplaceDetailTaskAdapter : IMarketplaceDetailTask
{
public void Show()
{
var marketplaceDetailTask = new MarketplaceDetailTask();
marketplaceDetailTask.Show();
}
}
The unit test compatible implementation of the IMarketplaceDetailTask
is called MockMarketplaceDetailTask
and allows a specified Action
to be invoked when the Show
method is called:
public class MockMarketplaceDetailTask : IMarketplaceDetailTask
{
readonly Action action;
public MockMarketplaceDetailTask(Action action)
{
this.action = action;
}
public void Show()
{
if (action != null)
{
action();
}
}
}
The ChatClientViewModel
class contains an ICommand
named BuyCommand
, which, when executed, retrieves the IMarketplaceDetailTask
from the IoC container and calls its Show
method. BuyCommand
is initialized in the viewmodel’s constructor, as shown:
buyCommand = new DelegateCommand(
obj =>
{
var marketplaceDetailTask
= Dependency.Resolve<IMarketplaceDetailTask>();
marketplaceDetailTask.Show();
});
When the BuyCommand
is executed, it causes the IMarketDetailTask
instance to be resolved using the static Dependency.Resolve
method. Recall that a particular implementation of the IMarketDetailTask
is registered according to the selected build configuration, as described in the earlier section, “A Custom IoC Container and DI Framework.” If the build is using a Release configuration, then an instance of the MarketplaceDetailTaskAdapter
is resolved, which, in turn, causes an instance of the built-in MarketplaceDetailTask
to be shown. Conversely, if the build is using a Debug configuration, then the MockMarketplaceDetailTask
is used, which does not disrupt unit testing or any manual ad hoc testing.
This chapter explored the Silverlight Unit Testing Framework (UTF) for Windows Phone. The chapter began with a walk-through of the creation of a unit test project. You saw how to create test classes and test methods and looked at the tag expressions editor, which provides a useful mechanism for selecting which tests are to be run during a test session.
The chapter then examined the UTF in greater detail by creating a simple chat client app. You saw how to test a viewmodel’s behavior before creating a UI and explored code driven UI testing. You also learned how to perform asynchronous testing and touched on the Microsoft Automation framework.
The chapter then examined some advanced topics such as Inversion of Control, mocking, and how to use a custom IoC container.
Finally, you saw how to perform testing of trial applications. You learned how to hide or reveal content based on a mock licensing service and examined a custom API for mocking launchers and choosers.
18.119.28.237